Saturday, September 15, 2007

Design Patterns in Python: II

As mentioned before, I am going through Part II of a talk on DPs in Python. The first part of the talk focuses on the Template Method design pattern. I have seen this pattern in the form of "algorithm" pattern in C++ template library and elsewhere (SAX parser library, various formatter libraries), but the video talk explains this very well. Alex Martelli offers "Self-Delgation" as an alternative name for this pattern. I suppose Algorithm could be another name, at least for some situations.

Well, we all know that the TM pattern concerns itself with separating the generic part of an algorithm or process into an "organizing method", which calls "hooks" at appropriate points to execute certain steps of the algorithm/process. So far, so good. What is revealing is that this separation can be done in three ways:

Inheritance TM

The organizing method is defined in an ABC. Sub-classes provide concrete implementations of hooks. Obvious variations: the parent class provides no-op, or reasonable default implementations of hooks. E.g., Queue.Queue follows the TM pattern and provides concrete hook implementations, which provide a full-implementation of a FIFO queue. However, because of the TM pattern, it can be sub-classed and the hooks overridden to make a LIFO queue, for e.g.

Factored TM

In this technique, the organizing method is in one class, and the hooks are implemented in another. The hook class can be passed to the class implementing the organizing method as an argument at instantiation time.

Further, hooks can be grouped. There can be a class per group.

Even further, each hook can be a class by itself. Then we get the Strategy pattern.

Mixin TM

This is applicable for languages allowing multiple inheritance. The idea is:

  • You multiply-inherit from a Mixin class that provides the "organizing methods"
  • You implement hooks to obtain the desired functionality.
An e.g.: you could derive your class from DictMixin, which causes your object to have the dictionary interface. All you have to do is implement a few hook methods, and automatically the rest of the rich dictionary interface (which is based on top of these hooks) is part of your class!

Combined Inheritance, Factored, and Mixin TM
One interesting use of these three techniques is in the SocketServer module:
  • TCPServer is a concrete implementations of network servers. They use factored TM: the TCP/UDPServer implements the organizing method, and a class derived from BaseRequestHandler provides the concrete hooks. Well, BaseRequestHandler provides some no-op hook implementations, that serve as documentation.
  • You can inherit from BaseRequestHandler and override some methods to specialize its behaviour. This is inheritance TM.
  • You can inherit from TCPServer and ThreadinMixin. ThreadingMixin overrides the TM of TCPServer and provides the multi-threaded TM. You just provide the hook implementation (the request handler) to have a multi-threaded tcp server.
The Strategy and State Patterns

The main idea in the Strategy Pattern seems to be: say you have a complicated process (the organizing method or OM) with many decision points. A decision point is impemented by an object which is invoked for action. That is, in TM we would have self.do_somthing(), but in Strategy we would have self.strat.do_something(). The strat is a reference to an object of some SC of a Strategy class that defines do_something(). To change the behaviour, we change strat from one SC instance to another. That is, we would do something like: self.strat = Strat1(), or self.strat = Strat2() depending on the situation. Obviously, Strat1 and Strat2 don't have to know much about each other for this to be scalable/appropriate.

In the State pattern, it is the state that knows how to switch behaviour. So we would have a decision point impemented as self.state.do_something(), and we would change behaviour by self.state.set_state(foo) or self.state.set_state(bar).

In Python, we can blur the lines:

1. Traditional

class Foo:
def bark():
print "bow"
class Bar:
def bark():
print "wow"
class OM:
def __init__():
self.strat = Foo()
def work():
print "working"
self.strat.bark()
print "work done"
def change_voice():
self.strat = Bar()

2. Change method

class OM:
def __init__():
def bark():
print "bow"
def bark2():
print "wow"
def work():
print "working"
self.bark()
print "work done"
def change_voice():
self.bark = self.bark2

3. Change __class__

class OM:
def work():
print "working"
self.bark()
print "work done"
def bark():
print "bow"
def change_voice():
self.__class__ = Cat

class Cat(OM):
def bark():
print "meow"

No comments: