Saturday, September 15, 2007

Design Patterns in Python: I

I found some time to catch up on general tech reading. Or rather, viewing. Because I have been viewing some excellent Google EngEDU videos.

I just watched "Advanced Topics in Programming Languages Series: Python Design Patterns (Part 1)". The presenter, Alex Martelli, emphasized that Design Patterns can be useful, useless or dangerous/overkill depending on the particular language of choice. In other words, as the A good example would be Singleton, which is probably conceptually broken. Python provides modules instead that work handily. I found myself remembering a particular use of Singleton in my own Python code that is, well, icky and reeks of uselessness. Anyway, Alex covered Creational Patterns in brief, and then went on to talk about Structural Patterns. They were more interesting and Alex gave some examples from the python library modules.

Inheritance vs Composition

Alex mentioned that object composition is favored over inheritance for most cases. By composing or "wrapping" we can do things that inheritance cannot, like selectively modifying the interface to an object, restricting the interface provided. (Inheritance cannot restrict the interface to the object).

Hold vs Wrap

So we go with composition. Composing objects leads to the choice of the "Hold vs Wrap" choice . I have faced this choice myself and I have seen the irritating consequences of the wrong choice.

Say I have to compose objects O with subobject S. Hold: object O has a reference to S, nothing more. Then I would access S as O.S.some_method(). Wrap: hold (maybe via a private name), and provide delegation, so that you can say O.some_method().

The latter choice brings us into the realm of the Structural Design Patterns. At this point I would say that it becomes quite irritating to see code like this: foo.bar.some_method(). Basically O is exposing its internal structure to its clients and violating the "Law of Demeter":http://en.wikipedia.org/wiki/Law_of_Demeter.

Creational Patterns

Alex discussed the Singleton pattern and how it has fundamentally a problem with inheritance. He clarified that instead of a Singleton, what is usually required is a shared-state. He introduced the Borg/Monostate pattern that solves the same problem with more control possible.

The next few patterns were the Factory series of patterns. Luckily, in Python, any callable can be passed into client code and can behave as an object factory or factory method. In fact, each class is a kind of factory, which can be customized to your heart's content by implementing/overriding the __new__() method.

Structural Patterns

The next few patterns to be discussed were:

  • Adapter: you have code expecting interface C, supplier code providing interface S (a superset of C), you write an adapter from C to S;
  • Facade: you have one or more objects; you create a single interface concentrating and simplifying the needed functionality for clients; a discussion of Facade was mentioned: http://www.tonymarston.net/php-mysql/design-patterns.html (I have not looked at it yet)
  • Bridge: you have several implementations of abstraction A, all expecting functionality F, and you have (or want to have) several implementations providing F, and you dont want the client code to know of/depend on the F implementation. To do this, make sure that each implementation of A holds a reference R to F, and accesses the functionality F only by delegating to R. A good example is the SocketServer module, where BaseServer is the abstraction A, BaseRequestHandler is the functionality F. The appropriate implementation of BaseRequestHandler is passed at creation time to the appropriate sub-class of BaseServer, which then keeps a reference to it (the R). When a new request handler is required, R() is called to create a new object. Alex mentions that this is rather like a factory (but then, callables are factories), and that it is typical of Bridge DPs in Python to keep reference to a class and instantiate from it later. I wonder what a bridge to do a ORM would look like. I am now a little bit more confident of digging into the guts of SQLAlchemy.
  • Decorator: reuse/tweak without inheritance. Difference from Adapter/Facade would be that the interface does not change, but new functionality is somehow added. A buffering adapter to a file object would be an example: the starting and resulting interface is the same, and the difference is only whether data is written to disk with or without buffering. The gzip module is an example.
  • Proxy: a proxy looks like a decorator but deals with lifetime, access, location issues rather than adding functionality. Hmm.. well, CORBA comes to mind.
I am now going to watch Part II of the same series, which should deal with Behavioral Patterns.

The slides used for the talk are available at http://www.aleax.it/goo_pydp.pdf (8 MB PDF file).

No comments: