views:

197

answers:

5

I have two classes: Account and Operator. Account contains a list of Operators. Now, whenever an operator (in the list) receives a message I want to notify Account object to perform some business logic as well.

I think of three alternatives on how to achieve this:

1) Hold a reference within Operator to the container [Account] object and call methods directly. Not absolutely good because of circular references.

2) Use events. As far as I know there is no built-in event handling mechanism in Python. So, this one is a bit tricky to implement.

3) Don't send messages to Operators directly. Instead, operate only Accounts, and within them, internally, handler operators. This one is a bit limiting because in this case I cannot pass around references to operators.

I wonder which approach is the most advantageous from the architectural point of view. How do you usually handle this task?

It would be great if you could point out snippets in Python.

+3  A: 

There is no "one-size-fits-all" solution for the Observer pattern. But usually, it's better to define an EventManager object where interested parties can register themselves for certain events and post these events whenever they happen. It simply creates less dependencies.

Note that you need to use a global EventManager instance, which can be problematic during testing or from a general OO point of view (it's a global variable). I strongly advise against passing the EventManager around all the time because that will clutter your code.

In my own code, the "key" for registering events is the class of the event. The EventManager uses a dictionary (event class -> list of observers) to know which event goes where. In the notification code, you can then use dict.get(event.__class__, ()) to find your listeners.

Aaron Digulla
Thanks for reminding of the name of the pattern.
SeasonedCoder
+3  A: 

I would use event handling for this. You don't have to implement it yourself -- I use pydispatcher for exactly this kind of event handling, and it's always worked very well (it uses weak references internally, to avoid the circular reference problem).

Also, if you're using a gui framework, you might already have an event framework you can hook into, for example PyQt has signals and slots.

dF
+1 for pydispatcher and its kin. It's simple to use and was designed for solving exactly the situation you're described. It decouples the objects in a way allowing you to hook up Operators inside other containers as well, if you need that
Eli Bendersky
+5  A: 

You're over-thinking this. Seriously. Python isn't C++; your concerns are non-issues in Python. Just write what makes sense in your problem domain.

" Not absolutely good because of circular references."

Why not? Circularity is of no relevance here at all. Bidirectional relationships are great things. Use them. Python garbage collects them just fine without any thinking on your part.

What possible problem do you have with mutual (birectional) relationships?

"...operate only Accounts, and within them, internally, handler operators. This one is a bit limiting because in this case I cannot pass around references to operators. "

What? Your Operators are Python objects, pass all you want. All Python objects are (in effect) references, don't sweat it.

What possible problem do you have with manipulating Operator objects?

S.Lott
+1 - As long as you're using Python 2.5 and not defining __del__ methods, circular references just aren't a big deal anymore.
Jason Baker
@Jason Baker: Been using Python heavily for 7 years, never defined a __del__ method. Never had a problem with bidirectional relationships, use them regularly. They worked great all the way back to 2.2.
S.Lott
Ok, didn't know that it tracks down all the circular references. But what if I do define __del__ method (for whatever reason)? I still have to track them manually, right?
SeasonedCoder
@S.Lott you are right about over-thinking. Sometimes I tend to think too much ahead ;)
SeasonedCoder
@seasonedcoder: you have no use for the __del__ method. None whatsoever. If you think you need it (a) you're doing something wrong and (b) post your goal and the code as a separate question so we can tell you what you're doing wrong.
S.Lott
+2  A: 
>>> class Account(object):
...     def notify(self):
...         print "Account notified"
...
>>> class Operator(object):
...     def __init__(self, notifier):
...         self.notifier = notifier
...
>>> A = Account()
>>> O = Operator(A.notify)
>>> O.notifier()
Account notified
>>> import gc
>>> gc.garbage
[]
>>> del A
>>> del O
>>> gc.garbage
[]

One thing you may not know about instance methods is that they're bound when looked up when using the dot syntax. In other words saying A.notify automatically binds the self parameter of notify to A. You can then hold a reference to this function without creating uncollectable garbage.

Lastly, you can always use Kamaelia for this type of thing.

Jason Baker
Wow, thanks for the code snippet and very clear explanation. Very interesting approach.
SeasonedCoder
A: 

There are Observer pattern snippets all over the Web. A good source of reliable code is active state, E.G :

http://code.activestate.com/recipes/131499/

e-satis