Python Design Patterns Best Practices (Refactoring Guru)
Implementation reference for the 22 Gang of Four design patterns in idiomatic modern Python (3.10+), distilled from refactoring.guru. Each of the 22 pattern files across 3 categories captures intent, problem, solution, applicability (when to use AND when NOT to), a runnable Python example with output, implementation steps, pros/cons, and relations to sibling patterns.
This is the Pythonic-first companion to the TypeScript design-patterns skill. Most GoF patterns shrink to a language feature in Python — a function, a generator, a dataclass, functools.singledispatch, a match statement. Every entry leads with that idiomatic form and keeps the class-based GoF structure only where identity, stored state, runtime registration, or polymorphic dispatch genuinely earn it.
The patterns are a vocabulary for structural decisions, not a prescription. Reach for one only when its applicability criteria match — every entry includes a When NOT to Use section to guard against over-engineering, which is the more common failure with this catalog in Python.
When to Apply
- A constructor has grown to 10+ parameters (telescoping-constructor smell) or subclasses exist only to bake in parameter combinations
- A method branches on
kind/type/mode/statusto pick an algorithm or behavior — amatchorif/elifladder that grows with each variant - Integrating an incompatible third-party API, library, or legacy class whose method names don't match your code
- Modeling a tree-shaped domain (file systems, ASTs, UI trees, org charts) where leaves and branches must be treated uniformly
- Adding cross-cutting behavior at runtime — logging, caching, access control, compression — without subclassing
- Selecting an algorithm or behavior variant at runtime from config, user input, or environment
- Implementing undo/redo, history snapshots, transactional rollback, or queueing/scheduling of operations
- Coordinating many objects whose direct mutual references have become tangled — a hub that brokers communication
- Notifying many subscribers when something changes — event systems, reactive data flows
- Reviewing code that smells like a pattern is implicit (a giant
if isinstance(...), parallel class hierarchies, copy-pasted algorithm skeletons) — make it explicit, or collapse it to a Python idiom
Rule Categories
| # | Category | Impact | Patterns | When to reach for this group |
|---|---|---|---|---|
| 1 | Creational | HIGH | 5 | Object construction is non-trivial, varies by configuration, or risks tight coupling to concrete classes |
| 2 | Structural | HIGH | 7 | Composing classes/objects into larger structures while keeping parts substitutable |
| 3 | Behavioral | HIGH | 10 | Distributing responsibility and defining how objects collaborate at runtime |
How to Use
- Recognize the shape. Read the Quick Reference below and identify which pattern's intent matches your problem. Most pattern-shaped problems sound like one of the listed phrases.
- Read the pattern reference. Open
references/{category}-{pattern}.md. Confirm intent, then read Applicability and When NOT to Use before adopting. - Lead with the idiom. Each "Correct" example shows the Pythonic form first. Adopt it unless you need the class-based structure shown in the Alternative block.
- Adapt to your domain. The examples use small realistic domains (transports, route planners, document trees). Rename to your terms before merging.
- Check the relations. Each entry ends with Related Patterns — siblings worth considering for the same problem.
Quick Reference
1. Creational Patterns (object instantiation)
creational-factory-method— Resolve a concrete class through a registry/dispatch dict. "I want to pick a class by config/string key without an if/elif ladder." — HIGHcreational-abstract-factory— Produce families of related objects that must match. "Switching one flag must swap a whole coordinated set (button + checkbox)." — MEDIUM-HIGHcreational-builder— Construct complex objects step by step — in Python a keyword-only dataclass first. "My constructor has 10+ params, or I need staged assembly." — HIGHcreational-prototype— Clone viacopy.deepcopy/dataclasses.replace. "I need another one just like this, with one value changed." — MEDIUMcreational-singleton— One shared instance via a module global orfunctools.cache. "I need exactly one config/registry/pool, kept testable." — MEDIUM
2. Structural Patterns (composition)
structural-adapter— Wrap a class so its interface matches what callers expect. "This library's method names don't match mine and I can't edit it." — HIGHstructural-bridge— Split abstraction from implementation via composition +Protocol. "Two orthogonal axes and the subclass count is exploding." — MEDIUMstructural-composite— Treat leaves and trees uniformly via a sharedProtocol+ recursion. "I have a tree and want one interface for items and groups." — HIGHstructural-decorator— Stack wrappers (or use@decorator) to add behavior at runtime. "I want to layer logging + caching + compression in any order." — HIGHstructural-facade— Expose one function/module over a complex subsystem. "I just wantconvert(file, fmt), not the codec/bitrate dance." — HIGHstructural-flyweight— Share immutable state via a cached factory +__slots__. "Millions of objects, only a few distinct payloads — out of RAM." — LOW-MEDIUMstructural-proxy— Stand in via__getattr__/cached_propertyto control access. "I need lazy loading / auth / caching without touching the real object." — MEDIUM-HIGH
3. Behavioral Patterns (collaboration)
behavioral-chain-of-responsibility— Run a request through an ordered list of handlers. "A pipeline of auth/validate/authorize checks I want to reorder." — MEDIUM-HIGHbehavioral-command— Reify a request as a callable/closure with optional undo. "I need undo/redo, queueing, or one action shared across UI surfaces." — HIGHbehavioral-iterator— Traverse via__iter__/generators without exposing internals. "I wantfor x in my_structureto just work." — HIGHbehavioral-mediator— Route component interaction through one hub. "My widgets all reference each other and nothing is reusable." — MEDIUMbehavioral-memento— Snapshot/restore state via a frozen dataclass. "I need undo/rollback without exposing private fields." — LOW-MEDIUMbehavioral-observer— Notify subscriber callbacks on change (often apropertysetter). "Many objects must react when one value changes — events, reactive UI." — CRITICALbehavioral-state— Delegate to polymorphic state objects (or an enum + dispatch). "My class is a state machine withif status ==in every method." — MEDIUM-HIGHbehavioral-strategy— Pass an algorithm as aCallableand swap it at runtime. "Multiple algorithms (sort/route/pay) picked without conditionals." — HIGHbehavioral-template-method— Fix a skeleton in an ABC; subclasses override steps. "Several classes share an algorithm with a couple of varying steps." — MEDIUMbehavioral-visitor— Add operations viafunctools.singledispatch/match. "I need 5 operations across an AST without editing the node classes." — LOW-MEDIUM
How to Choose Between Similar Patterns
Several patterns share a shape but solve different problems. Read each pattern's Related Patterns section, then apply these distinctions:
- Adapter vs. Facade vs. Proxy vs. Decorator — all four wrap a target. Adapter changes the interface. Facade simplifies a subsystem. Proxy keeps the interface and controls access/lifecycle. Decorator keeps the interface and adds behavior recursively.
- Strategy vs. State — both delegate to a swapped object. Strategy variants are independent functions the caller picks. State objects know each other and trigger transitions on the context.
- Strategy vs. Template Method — both vary parts of an algorithm. Strategy uses composition — a
Callableswapped at runtime. Template Method uses inheritance — an ABC skeleton fixed at definition time. - Factory Method vs. Abstract Factory vs. Builder — Factory Method resolves one product (a registry/
@classmethod). Abstract Factory returns a family of matching products. Builder assembles one complex product (a keyword-only dataclass, or a fluent builder for staged construction). - Composite vs. Decorator — both wrap children recursively. Composite aggregates child results. Decorator adds one responsibility and passes through.
- Chain of Responsibility vs. Command vs. Mediator vs. Observer — all connect senders and receivers. CoR passes a request along a list of handlers (and may stop early). Command makes the request a first-class callable. Mediator centralizes many-to-many communication. Observer establishes one-publisher-to-many-subscribers notification.
- Visitor:
singledispatchvs.matchvs. methods — usefunctools.singledispatchto add operations over a closed type set without editing the classes; usematchwhen you'd rather keep all cases in one exhaustive function; use plain methods when there's one operation and the type set is small.