Intuition

Every experienced developer notices the same problems appearing across unrelated projects - how to create objects without hardcoding classes, how to compose structures flexibly, how to let objects communicate without tight coupling. Design patterns name these recurring situations and provide tested blueprints for resolving them. They are not libraries or frameworks; they are shared vocabulary for design decisions.

The Gang of Four (Gamma, Helm, Johnson, Vlissides) catalogued 23 patterns in 1994, grouped by purpose. The catalog remains influential because the problems it addresses - decoupling creation from use, composing behavior at runtime, managing complex interactions - persist regardless of language or era.


Core Idea

Creational Patterns

Creational patterns abstract the instantiation process, decoupling what gets created from how.

PatternIntent
Factory MethodDefine an interface for creating objects; let subclasses decide the concrete class.
Abstract FactoryProvide families of related objects without specifying concrete classes.
BuilderSeparate construction of a complex object from its representation.
PrototypeCreate new objects by cloning an existing instance.
SingletonEnsure a class has exactly one instance with a global access point.

Structural Patterns

Structural patterns deal with composing classes and objects into larger structures while keeping them flexible.

PatternIntent
AdapterConvert one interface into another that clients expect.
BridgeSeparate abstraction from implementation so both can vary independently.
CompositeTreat individual objects and compositions uniformly via a tree structure.
DecoratorAttach additional responsibilities dynamically, without subclassing.
FacadeProvide a simplified interface to a complex subsystem.
ProxyControl access to an object through a surrogate.

Behavioral Patterns

Behavioral patterns manage algorithms, responsibilities, and communication between objects.

PatternIntent
ObserverDefine a one-to-many dependency so dependents update automatically.
StrategyEncapsulate interchangeable algorithms behind a common interface.
CommandEncapsulate a request as an object, enabling undo and queuing.
IteratorProvide sequential access to elements without exposing internals.
StateLet an object alter its behavior when its internal state changes.
Template MethodDefine an algorithm skeleton; let subclasses fill in specific steps.
VisitorAdd operations to object structures without modifying the classes.

Note

Patterns are language-agnostic in concept, but some become unnecessary in languages with first-class functions, traits, or algebraic data types. Strategy, for instance, collapses to a function parameter in most functional languages.


Example

The Observer pattern in action - a weather station notifying multiple displays:

class WeatherStation:
    def __init__(self):
        self._observers = []
        self._temperature = 0.0
 
    def attach(self, observer):
        self._observers.append(observer)
 
    def set_temperature(self, temp):
        self._temperature = temp
        for obs in self._observers:
            obs.update(temp)
 
class PhoneDisplay:
    def update(self, temp):
        print(f"Phone: {temp}°C")
 
class WebDashboard:
    def update(self, temp):
        print(f"Dashboard: {temp}°C")
 
station = WeatherStation()
station.attach(PhoneDisplay())
station.attach(WebDashboard())
station.set_temperature(22.5)
# Phone: 22.5°C
# Dashboard: 22.5°C

The station knows nothing about display internals - it only calls update. New displays can be added without changing WeatherStation. This is the core payoff: extensibility without modification.

Tip

Before reaching for a pattern, ask whether the language already provides the mechanism natively. Python’s @property eliminates many uses of Proxy; Rust’s enum with match often replaces Visitor.