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)
- 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