| name | design-patterns-gof |
| description | The 23 Gang of Four object-oriented design patterns (Gamma, Helm, Johnson, Vlissides, 1994) distilled as a practical field guide, not a catalog. Use when naming a shape in code review, choosing between competing designs, deciding whether to introduce indirection, or auditing for over-abstraction. Each pattern documents intent, tradeoffs, modern relevance, language-level replacements, and when NOT to use it. Triggers on: "design pattern", "GoF", "factory", "builder", "singleton", "adapter", "decorator", "observer", "strategy", "visitor", "state machine", "command pattern", "too many constructors", "subclass explosion", "program to an interface", "composition over inheritance", "am I over-abstracting this", "Swift design patterns", "protocol-oriented programming", "POP in Swift", "Crusty talk", "protocol vs class", "value types over reference types", "start with a protocol". |
Design Patterns (Gang of Four)
Field guide to the 23 patterns from Design Patterns: Elements of Reusable Object-Oriented
Software (Gamma, Helm, Johnson, Vlissides, 1994), updated for modern TypeScript/Python/Rust/Swift
practice. Each pattern lives in its own reference file — this SKILL.md is the index and the
meta-level (principles, categories, decision tree, criticism).
Why use this skill
- You want the vocabulary — naming a shape ("this is a Decorator chain") beats a paragraph of prose in PR review.
- You're about to introduce an abstraction and want to sanity-check whether it earns its keep.
- You're reading a codebase that leans on patterns and need a map.
- You're translating a C++/Java pattern into a language with first-class functions and sum types.
This skill is opinionated. In 2026, a lot of GoF ceremony dissolves into language features
(closures, discriminated unions, generators, pattern matching, DI containers). The patterns that
survived kept their names because the shape is useful; the class hierarchies often aren't.
The two core principles (the spine of the book)
Every pattern in the book is a recipe for one or both of these:
- Program to an interface, not an implementation. Depend on the abstract contract, not the
concrete class. You can swap implementations, test with fakes, and change internals without
touching callers.
- Favor object composition over class inheritance. Inheritance is white-box reuse — the
subclass sees the parent's internals and is tightly coupled. Composition is black-box reuse —
you hold a reference and call its interface. Most behavioral patterns exist to express
composition in a language that defaulted to inheritance.
When a pattern feels wrong, it's usually because one of these principles was violated (e.g.,
Template Method forces inheritance; Singleton exposes global state and hides dependencies).
The four elements of a pattern (GoF Ch. 1)
Every pattern documented here has:
- Name — handle for design conversation. The vocabulary is the real product.
- Problem — the context and preconditions that make the pattern applicable.
- Solution — the participants, relationships, and collaborations. A template, not code.
- Consequences — tradeoffs. Most important and most often stripped from tutorials.
A pattern without stated consequences is a cargo cult.
The 23 patterns at a glance
Creational (5) — object construction
| Pattern | One-line intent | Modern relevance |
|---|
| Abstract Factory | Produce families of related objects through one interface | Low-mid. DI containers subsume most uses. |
| Builder | Construct complex objects step-by-step; avoid telescoping constructors | High (fluent APIs everywhere). Rust-canonical. |
| Factory Method | Subclass (or function) decides which concrete class to instantiate | High. Array.of, Promise.resolve, URL.createObjectURL. |
| Prototype | Clone an existing instance instead of constructing from scratch | Low in app code; high in game engines / scene graphs. |
| Singleton | One instance, global access | Mostly anti-pattern. Use DI or module-level values. |
Structural (7) — object composition
| Pattern | One-line intent | Modern relevance |
|---|
| Adapter | Translate one interface to another so incompatible objects can collaborate | Ubiquitous at system boundaries. |
| Bridge | Split abstraction and implementation into two hierarchies linked by composition | Moderate. JDBC, React renderers. |
| Composite | Treat individual objects and groups uniformly via a tree | Ubiquitous. DOM, ASTs, scene graphs. |
| Decorator | Stack behaviors onto an object at runtime | Ubiquitous (middleware under different names). |
| Facade | One narrow API over a complex subsystem | Very high. SDK clients, service classes. |
| Flyweight | Share fine-grained objects to save memory | Niche. Graphics, text rendering, ECS. |
| Proxy | Surrogate that controls access to another object | Enormous. ORMs, gRPC stubs, Vue/MobX reactivity. |
Behavioral (11) — communication and responsibility
| Pattern | One-line intent | Modern relevance |
|---|
| Chain of Responsibility | Pass a request through a chain of handlers | Ubiquitous as middleware. |
| Command | Encapsulate a request as a first-class object | High (CQRS, Redux actions, job queues). |
| Interpreter | Represent a grammar and evaluate sentences in it | Low. Use parser combinators / ADTs. |
| Iterator | Traverse a collection without exposing its structure | Built into every modern language. |
| Mediator | Central hub coordinates peers so they don't talk directly | Moderate. Redux/Zustand stores, chat servers. |
| Memento | Capture and restore an object's state without breaking encapsulation | High (undo, time travel, transactions). |
| Observer | Publisher notifies many subscribers of state changes | Ubiquitous (EventEmitter, RxJS, signals). |
| State | Object's behavior changes with internal state via delegation | High. XState, typestate, UI state machines. |
| Strategy | Interchangeable algorithms behind one interface | Ubiquitous — usually just a function. |
| Template Method | Fixed algorithm skeleton; subclasses override steps | Mostly framework internals. Prefer Strategy. |
| Visitor | Add operations to an object structure without modifying it | Niche (compilers). Prefer pattern matching. |
Decision tree — which category to look in
Is the pain about...
├─ CONSTRUCTION? → Creational
│ • Too many constructor args / optional config? → Builder
│ • Need to swap families together? → Abstract Factory
│ • Subclass decides concrete type? → Factory Method
│ • Construction expensive / copy cheaper? → Prototype
│ • (Singleton: almost never — use DI.)
│
├─ SHAPE / WIRING? → Structural
│ • Incompatible interfaces? → Adapter
│ • Add responsibility without subclassing? → Decorator
│ • Tree / part-whole with uniform treatment? → Composite
│ • Simplify access to subsystem? → Facade
│ • Control access / lazy / remote? → Proxy
│ • Decouple two hierarchies (what vs. how)? → Bridge
│ • Share millions of fine-grained objects? → Flyweight
│
└─ BEHAVIOR / COMMS? → Behavioral
• Swap algorithm at runtime? → Strategy (usually just a function)
• Broadcast to many listeners? → Observer
• Encapsulate request (queue / undo / log)? → Command (+ Memento for undo)
• Step through a collection uniformly? → Iterator (usually built-in)
• Walk a heterogeneous tree? → Visitor (prefer pattern matching)
• Object acts differently per internal mode? → State
• Skeleton with customizable steps? → Template Method (prefer Strategy)
• Many-to-many chatter → one hub? → Mediator
• Try handlers until one succeeds? → Chain of Responsibility
• Capture/restore state? → Memento
• Evaluate a domain grammar? → Interpreter (prefer parser combinators)
Before committing to any leaf, run two checks:
- Does my language already have this? Strategy = function. Iterator =
for/generator.
Command = closure + data. Observer = pub/sub primitive. Visitor = match on union. If the
language answers, stop.
- Have I seen this shape three times? (Rule of Three, Fowler.) If not, inline it. Two
occurrences aren't enough signal — the "common" shape is usually an illusion, and the wrong
abstraction is more expensive than duplication (Sandi Metz).
How patterns combine (the real value)
Real systems stack 3–5 patterns. Key relationships from GoF's inter-pattern graph:
- Abstract Factory is often built with Factory Methods or Prototypes.
- Composite is almost always traversed with Iterator or Visitor.
- Decorator and Composite share recursive structure; differ in intent (augment vs. aggregate).
- Command + Memento = undo stack.
- Chain of Responsibility is often built on Composite (the tree is the chain).
- Mediator often uses Observer internally.
- State and Strategy share a class diagram; differ in who drives transitions.
- Visitor + Composite + Iterator is the compiler-writer's trio.
- Interpreter's AST is a Composite.
When to reach for a pattern — and when you're over-abstracting
Signs you're over-abstracting:
- One concrete implementation behind a factory/interface. Delete the indirection.
- Class names like
AbstractSingletonProxyFactoryBean — the pattern chain is the identity.
- "We'll need it later." You won't. YAGNI.
- The pattern appears before the duplication (UML before the second caller).
- Anemic domain: all behavior lives in services, entities are getter-bags. No pattern fixes a modeling problem.
Signs you actually need it:
- A shape has repeated three times with real variation.
- Multiple actual implementations exist today (payment providers, renderers, formats).
- You want a specific consequence (undo, part-whole uniformity, runtime algorithm swap).
- The team needs a shared name to stop re-deriving the design in every review.
Language-specific guides
- Swift — references/swift-idioms.md maps every GoF pattern to
its Swift 5.10 / Swift 6 / iOS 17+ replacement (enum with associated values,
@Observable,
@resultBuilder, actors, property wrappers, Codable, AsyncSequence, @dynamicMemberLookup,
structured concurrency, copy-on-write). Answers "is this pattern still idiomatic in Swift?" per
pattern.
- Swift — Protocol-Oriented Programming — references/swift-protocol-oriented.md
distills Dave Abrahams' WWDC 2015 "Crusty" talk (Session 408) and a decade of community
retrospective. Covers why value types + protocols + extensions dissolve Template Method, Strategy,
Abstract Factory, Bridge, Adapter, Visitor, Iterator — and the POP failure modes (protocol soup,
PAT friction,
some/any confusion) that led Apple to bring classes back via @Observable /
@Model. Pair with swift-idioms.md: that file answers "what's the Swift replacement?"; this
file answers "why does it work, where does it fail, when do I still use a class?"
Criticism and modernity — essential context
Three long-running critiques have become conventional wisdom; treat them as load-bearing:
- Patterns as workarounds for weak languages (Paul Graham, "Revenge of the Nerds"). If you're
hand-expanding a template, your language is missing a construct.
- Patterns dissolve in expressive languages (Peter Norvig, 1996). 16 of 23 GoF patterns are
invisible or trivial in Lisp/Dylan. In 2026 most of those language features exist in
TS/Python/Rust/Swift/Kotlin — so the class hierarchies dissolve while the shape names stay
useful.
- Cargo cult risk (Jeff Atwood). Patterns as templates-to-apply rather than tools-for-problems
produce classes whose names leak implementation rather than naming domain concepts.
Full treatment in references/criticism-and-modernity.md.
What's still canonical vs. what aged poorly
Still alive under their GoF names: Adapter, Facade, Proxy, Decorator, Observer, Composite,
Iterator (as protocol), Strategy (as function), Builder, Factory Method, State, Command, Chain of
Responsibility, Memento.
Mostly anti-patterns or heavily displaced:
- Singleton — global state, test-hostile. Replaced by DI or module-level values.
- Visitor — replaced by pattern matching on sealed hierarchies / discriminated unions.
- Template Method — violates composition-over-inheritance. Prefer Strategy.
- Abstract Factory — absorbed by DI containers.
- Interpreter — parser combinators and ADT + recursive
eval are better.
- Prototype — niche outside game engines; immutable data makes it redundant.
How to use this skill
- Identify the axis of pain (construction / structure / behavior) using the decision tree.
- Read the candidate pattern's reference file. Look at Modern Relevance and When NOT to Use
before you look at the structure — those sections tell you whether to even continue.
- If you adopt the pattern, prefer the language-native form (function, union, closure) to the
class-heavy GoF form unless you actually need the ceremony (serialization, persistence, cross-
language interop, explicit dispatch).
- Name it in code and PRs. The vocabulary is half the point.