Design Patterns in Python

If it's about anything other than trains, it goes here
Home | More Geekery

Design Patterns in Python

Here, are the “Gang-of-Four” design patterns, which are used heavily in some languages (notably Java, .Net and C++).

This commentary is mainly a discussion of how these patterns are useful (or not) in Python.

The most commonly used design patterns

  • Bug Ball Of Mud
  • Spaghetti
  • MVC

Thanks to Stack exchange for a light-hearted introduction, now, on with the serious stuff!

Creational Patterns (x5)

Abstract Factory | Builder | Factory Method | Prototype | Singleton | (Lazy)
Abstract Factory

This appears to be an Abstract base class for a factory, i.e. it's "just" inheritance. Mark Summerfield's article at informit.com suggests that one factory class can serve as the base class of the other factory class, but this isn't the point of an Abstract Base class.

Note: Abstract base classes themselves (ABCs) are part of the Standard Library

Builder

The builder pattern exists to solve a problem of "exponential list of constructors", i.e. if your class has a lot of contstructor options, this could be a useful way of refactoring (see the example in the wikipedia article, linked above). It also means you can end up with a cleaner API for building a complex object.

Factory Method

There seems to be an issue with the "new" operator in Java and C++, where constructing an instance by saying Foo f = new Foo(); is Considered harmful. It seems to hinge on the notion that "you might want to change Foo to one of its subtypes at a later date", and therefore you should use a Factory instead of the "new" operator (e.g. here and here). My view is that, if you think it might be needed later, you should code it later, and stick to doing just what is required (see the Just barely good enough principle)

"new" is also considered harmful in Javascript, and in the use of Mixins in Java

However, in Python, where object creation is cheap, and mixins are easy, the use of a Factory may be less critical than in other languages. Furthermore, a Factory should only be considered at the point that the code becomes more complex, at which point, modules like Factory Boy" come in handy.

In the simple example below, we define 2 classes with the same interface. We can then choose our animal at runtime (assume 'x' is set); note we don't need a Factory Class to do this at all; it could be handled in a module, for example.

class Cat(object):
    def shout(self):
        return "Meow"

class Dog(object):
    def shout(self):
      return "Woof"

# ...

# Choose your object type at 'runtime'
if x:
    what_i_want = Cat 
else:
    what_i_want = Dog

# Any pet can make a noise. Look!
my_pet = what_i_want()
my_pet.shout()


                           

Prototype

Particularly useful when instance creation would be expensive

Singleton

Possibly the most abused pattern; easy to understand, easy to over-use, where something simple (e.g. a global variable) might actually be more appropriate.

In Python, a Singleton could be expressed as a class definition with only '@classmethod's (as suggested here), which would be analogous to Javascript's Math object, which has methods like Math.sqrt();. While this may make sense in Javascript, Java and C++ where everything has to be an object, it seems more reasonable to simply use a module in Python.

A module such as os.path in the standard library, contains a set of functions to manipulate file system pathnames, e.g.

  • os.path.exists(path)
  • os.path.join(path, *paths)
os.path is a module, not a class; an instance cannot be created. It performs the same function as a singleton in that you can't have more than one, but it's not a singleton-as-an-object.

Bonus entry: Lazy Initialization

Mentioned in GoF in terms of the "Proxy" pattern ("Stuctural", see below), and in the wikipedia article for singleton.

A notable use in Python is in internationalisation functions. See for example the Django documentation on "lazy translations". The idea of making translations "lazy" is to wait until the language locale is known before rendering the text.

Structural Patterns (x7)

Adapter
makes incompatible things compatible
Bridge
Composite
Decorator

The @decorator is Built into Python since 2.4, however this is not 100% consistent with the Decorator pattern.

Facade
used when an easier or simpler interface to a more complex underlying object is desired
Flyweight
Proxy

Behavioral Patterns (x11)

Chain of responsibility
Command
Interpreter
Mediator
Memento
Observer
State
Strategy

lets the algorithm for a routine be selected at runtime

I used this pattern where one of two algorithms could be selected at runtime. It ultimately proved too complex and was replaced by an "if" statement!

Template method
Visitor

References

Creational Design Patterns in Python (Mark Summerfield)
Design Patterns in Python (Vespe Savikko)
Choice of implementation language affects the use of design patterns
The Most important patterns (Adam Bien)
There are some interesting criticisms of patterns in the comments. e.g. "Patterns are mostly just a workaround for weaknesses in a language or environment", "Most patterns are just workarounds"
en.wikipedia.org/wiki/Software_design_pattern
coderanch.com/t/617438/patterns/design-pattern-developer-aware
www.learningtree.com/courses/516/java-best-practices-and-design-patterns
www.toptal.com/python/python-design-patterns
www.blackwasp.co.uk/gofpatterns.aspx

:thumbsup: