Skip to content

Design patterns

A design pattern is a named, reusable solution to a problem that keeps coming up in software design. The name is half the value: saying “put an adapter in front of it” communicates a whole design in four words.

The best-known catalogue is Design Patterns: Elements of Reusable Object-Oriented Software (1994) by Gamma, Helm, Johnson and Vlissides, the “Gang of Four” (GoF). It describes 23 patterns in three groups.

Creational patterns

These control how objects are made, so the code that uses an object does not depend on exactly which class it gets.

Pattern Use it when
Factory method A class needs to create objects but subclasses should decide which kind
Abstract factory You need families of related objects that must match (for example, one per hardware board)
Builder An object has many optional parts and a constructor would be unreadable
Prototype Copying an existing object is cheaper or simpler than building one
Singleton There must be exactly one instance. Use sparingly: it is global state with a nicer name

The creational patterns.

Structural patterns

These are about composing classes and objects into larger structures.

Pattern Use it when
Adapter You need to use a class whose interface does not match what your code expects
Bridge Both an abstraction and its implementation need to vary independently
Composite Clients should treat a single item and a group of items the same way (trees)
Decorator You want to add behaviour to an object without subclassing it
Facade A subsystem is complicated and most callers need a simple front door
Flyweight You have very many similar objects and need to share their common state
Proxy Access to an object needs control: lazy loading, caching, remote calls, permissions

The structural patterns.

Behavioural patterns

These are about how objects share responsibility and communicate.

Pattern Use it when
Chain of responsibility Several handlers might deal with a request and the sender should not care which
Command You want to treat a request as an object: queue it, log it, undo it
Interpreter You have a small language to evaluate
Iterator Clients need to walk a collection without knowing its structure
Mediator Many objects talk to each other and the wiring has become a tangle
Memento You need to save and restore an object’s state without exposing its internals
Observer Other objects must be told when something changes (the basis of event handling)
State An object’s behaviour depends on its state and the if statements are spreading
Strategy You need to swap one algorithm for another at run time
Template method The steps of an algorithm are fixed but some steps vary
Visitor You need to add operations to a stable set of classes without changing them

The behavioural patterns.

A small example

The strategy pattern in Python, choosing how to compress a log file:

from typing import Protocol
import gzip, lzma

class Compressor(Protocol):
    def compress(self, data: bytes) -> bytes: ...

class Gzip:
    def compress(self, data: bytes) -> bytes:
        return gzip.compress(data)

class Xz:
    def compress(self, data: bytes) -> bytes:
        return lzma.compress(data)

def archive(data: bytes, compressor: Compressor) -> bytes:
    return compressor.compress(data)

The strategy pattern: the caller picks the algorithm.

Using patterns well

  • Start from the problem, not the pattern. If you cannot name the force a pattern resolves, you probably do not need it.
  • Modern languages absorb some patterns. First-class functions make many strategies and commands a plain function; iterators are built into most languages.
  • Patterns in embedded C look different (function pointer tables, static allocation) but the ideas carry over.
  • Overuse is its own anti-pattern: an abstract factory for one product is just extra code.

Where next