technology 1994

Design Patterns

by Erich Gamma, Richard Helm, Ralph Johnson, John Vlissides
Recurring problems in software design have recurring solutions — design patterns are named, cataloged, and explained templates for solving common structural, behavioral, and creational challenges in object-oriented systems.
design patterns object-oriented software architecture GoF software engineering reusable design

One-sentence summary: Recurring problems in software design have recurring solutions — design patterns are named, cataloged, and explained templates for solving common structural, behavioral, and creational challenges in object-oriented systems.

Key Ideas

1. Patterns as a Shared Vocabulary for Design

The Gang of Four's (GoF) most lasting contribution may not be any individual pattern but the concept of a pattern language for software design. Before this book, experienced developers solved the same problems repeatedly but had no shared vocabulary for the solutions. One programmer's "wrapper class" was another's "adapter" and a third's "translator." The GoF gave these recurring solutions canonical names, precise descriptions, and standardized documentation — transforming implicit tribal knowledge into an explicit, teachable discipline.

The power of a shared vocabulary extends far beyond code. When a developer says "we should use an Observer here" or "this is a classic Strategy pattern," they communicate a complete design concept — intent, structure, participants, trade-offs — in a single phrase. Without the vocabulary, the same discussion would require drawing diagrams, writing pseudocode, and debating alternatives for minutes or hours. Patterns compress design conversations the same way technical jargon compresses any specialized discussion — they encode complex ideas into efficient tokens.

The GoF were explicit that patterns are not inventions but discoveries. They didn't create these solutions; they observed them recurring across different systems, different languages, and different domains. The Observer pattern exists in GUI frameworks, event systems, publish-subscribe architectures, and reactive programming — not because programmers read the GoF book, but because the problem (notifying multiple objects of state changes) recurs everywhere, and the solution converges on the same structure. Cataloging patterns is like cataloging species — you're documenting what already exists in the wild.

Practical application: Learn the names and intents of the 23 GoF patterns even before you master their implementation details. The vocabulary alone will transform your design discussions. When you encounter a design problem, ask: "Is this a known problem? Does a pattern exist for it?" Often, the answer is yes, and the pattern provides not just a solution but documentation of its trade-offs, consequences, and known variations.

2. Program to an Interface, Not an Implementation

The single most important design principle in the book — more fundamental than any individual pattern — is "program to an interface, not an implementation." This means that client code should depend on abstract interfaces (what an object can do) rather than concrete classes (how it does it). When code depends on interfaces, you can swap implementations without modifying the client — the system becomes flexible, testable, and extensible.

This principle is the foundation of almost every pattern in the catalog. Strategy works because the client depends on a strategy interface, not a specific algorithm. Observer works because subjects depend on an observer interface, not specific observers. Factory Method works because client code depends on a product interface, not a specific product class. The principle operates at every scale: within a single class (depend on abstractions of collaborators), between modules (communicate through interfaces), and across system boundaries (define contracts, not implementations).

The practical consequence is profound: designing with interfaces creates "seams" in the code — points where one implementation can be replaced by another. These seams are what make code testable (swap a real database for a mock), extensible (add new behaviors without modifying existing code), and maintainable (change how something works without changing the code that uses it). The absence of interfaces creates rigidity — every change ripples through the system because everything depends on everything else's concrete details.

Practical application: Whenever you create a class that will be used by other classes, ask: "Should clients depend on this concrete class, or on an interface that this class implements?" If the answer could ever be "there might be another way to do this," extract an interface. In practice, this means using dependency injection, defining clear contracts between modules, and resisting the temptation to call concrete methods directly when an abstraction would serve.

3. Favor Composition Over Inheritance

The GoF's second foundational principle is "favor object composition over class inheritance." Inheritance — the "is-a" relationship — is the first tool most object-oriented programmers reach for when they want to reuse behavior. But inheritance creates tight coupling between parent and child classes, makes the system brittle (changes to the parent ripple to all children), and locks the structure at compile time. Composition — the "has-a" relationship — assembles behavior from smaller, interchangeable parts and can be reconfigured at runtime.

The Strategy pattern exemplifies this principle perfectly. Instead of creating a SortableList subclass for each sorting algorithm (BubbleSortList, QuickSortList, MergeSortList), you compose a List with a SortStrategy object that can be swapped dynamically. Instead of inheriting behavior, the list delegates to a composable component. The result is fewer classes, more flexibility, and the ability to change behavior at runtime rather than being locked into a compile-time hierarchy.

The GoF were not anti-inheritance — they used it extensively in their patterns. Their argument was that inheritance should be reserved for expressing genuine type relationships ("a Square is a Shape") and should not be used as a mechanism for code reuse. When you inherit to reuse behavior, you create a permanent, structural dependency on the parent class's implementation. When you compose to reuse behavior, you create a flexible, pluggable relationship that can be changed independently. The distinction is between reusing a type (inheritance) and reusing a capability (composition).

Practical application: When you find yourself creating a class hierarchy primarily to share behavior, stop and ask: "Could I achieve this with composition instead?" If class B needs some of class A's behavior but isn't truly a subtype of A, extract the shared behavior into a separate object and compose both A and B with it. The Decorator, Strategy, and State patterns are all composition-based alternatives to what would otherwise be deep inheritance hierarchies.

4. Creational Patterns: Controlling How Objects Are Born

The GoF catalog divides patterns into three categories. Creational patterns address how objects are instantiated — abstracting the creation process so that systems can be independent of how their objects are created, composed, and represented. The five creational patterns are Abstract Factory, Builder, Factory Method, Prototype, and Singleton.

The central problem creational patterns solve is rigid coupling to concrete classes. When code says new ConcreteProduct(), it is permanently bound to that specific class. If the product needs to change — different implementation for different platforms, different configuration for different environments, different variants for different customers — every new call must be found and modified. Creational patterns encapsulate the creation logic so that the rest of the system remains oblivious to which specific classes are being instantiated.

Factory Method and Abstract Factory are the most widely used. Factory Method defines an interface for creating an object but lets subclasses decide which class to instantiate — the framework calls the factory method, and the application supplies the specific factory. Abstract Factory provides an interface for creating families of related objects without specifying their concrete classes — useful when a system must work with multiple product families (e.g., UI toolkits for different operating systems). Builder separates the construction of a complex object from its representation, enabling the same construction process to create different representations.

Practical application: Use Factory Method when your code creates objects but the specific type should be determined by subclasses or configuration. Use Builder when constructing complex objects with many optional parameters — it produces more readable code than constructors with many arguments. Use Singleton sparingly and with awareness of its costs (global state, testing difficulty, hidden dependencies). Default to the simplest creation pattern that eliminates the concrete coupling.

5. Structural Patterns: Organizing Relationships Between Objects

Structural patterns describe how classes and objects are composed to form larger structures. They use inheritance and composition to create new functionality from existing components. The seven structural patterns are Adapter, Bridge, Composite, Decorator, Facade, Flyweight, and Proxy.

Adapter and Facade are the most immediately practical. Adapter converts the interface of one class into another interface that clients expect — it's the software equivalent of a power plug adapter. When you integrate a third-party library whose API doesn't match your system's expectations, you write an Adapter. Facade provides a simplified interface to a complex subsystem — a single entry point that hides the subsystem's internal complexity. When your payment processing involves six different services, a PaymentFacade with a single processPayment() method shields the rest of the system from that complexity.

Decorator and Composite are the most structurally elegant. Decorator attaches additional responsibilities to an object dynamically, providing a flexible alternative to subclassing. Java's I/O streams are the classic example: new BufferedReader(new InputStreamReader(new FileInputStream("file.txt"))) — each wrapper adds a capability without modifying the underlying stream. Composite lets you treat individual objects and compositions of objects uniformly through a common interface — the file system (files and directories) and UI component trees are canonical examples.

Practical application: When integrating external systems or legacy code, reach for Adapter to translate interfaces rather than modifying either side. When a subsystem's complexity is leaking into client code, introduce a Facade. When you need to add capabilities to objects without creating an explosion of subclasses, use Decorator. When dealing with tree structures (UI components, file systems, organizational hierarchies), consider Composite.

6. Behavioral Patterns: Managing Algorithms and Responsibilities

Behavioral patterns are concerned with algorithms, assignment of responsibilities between objects, and patterns of communication. They describe not just objects and classes but the patterns of interaction between them. The eleven behavioral patterns include Chain of Responsibility, Command, Iterator, Mediator, Memento, Observer, State, Strategy, Template Method, and Visitor.

Observer and Strategy are arguably the two most influential patterns in modern software. Observer defines a one-to-many dependency between objects so that when one object changes state, all its dependents are notified automatically. It's the foundation of event-driven programming, reactive systems, the Model-View-Controller architecture, and every publish-subscribe system. Strategy defines a family of algorithms, encapsulates each one, and makes them interchangeable — it lets the algorithm vary independently from clients that use it. Together, Observer and Strategy enable the kind of loosely-coupled, pluggable, event-driven architectures that dominate modern software.

State and Command are underappreciated but extremely powerful. State allows an object to alter its behavior when its internal state changes — the object appears to change its class. Instead of complex if-else chains checking the current state, each state is a separate object with its own behavior. Command encapsulates a request as an object, allowing you to parameterize clients with different requests, queue or log requests, and support undoable operations. Every undo/redo system, every task queue, and every macro-recording system is built on the Command pattern.

Practical application: When you find yourself writing complex conditional logic that checks the "type" or "state" of an object to determine behavior, consider Strategy (for interchangeable algorithms) or State (for state-dependent behavior). When you need to decouple event sources from event handlers, use Observer. When you need to encapsulate actions as objects (for queuing, logging, or undo), use Command. The behavioral patterns are the most frequently applicable in daily coding.

7. Patterns Are Not Recipes: The Importance of Context and Judgment

The GoF were emphatic that patterns are not cookie-cutter solutions to be applied mechanically. Each pattern has a specific context (the problem it solves), forces (the competing concerns it balances), and consequences (the trade-offs it introduces). Applying a pattern in the wrong context — or applying too many patterns — is worse than applying none. The book explicitly warns against "pattern-itis," the tendency of developers who've just learned patterns to see them everywhere and apply them unnecessarily.

A pattern introduces structural complexity — additional classes, interfaces, and indirection. This complexity is justified only when it solves a real problem. Using a Strategy pattern where a simple if-else would suffice adds classes, interfaces, and cognitive overhead for zero benefit. Using an Abstract Factory when there's only one product family adds a layer of abstraction that no one will ever need. The art of design patterns is not knowing the patterns but knowing when to use them — and equally importantly, when not to.

The GoF also recognized that patterns exist at different scales and levels of abstraction. The 23 patterns in the catalog are mid-level design patterns — larger than idioms (language-specific coding conventions) but smaller than architectural patterns (MVC, microservices, event-driven architecture). Understanding this hierarchy prevents the mistake of trying to solve architectural problems with design patterns or language-level problems with architectural patterns. Each level has its own appropriate tools.

Practical application: Before applying a pattern, articulate the specific problem it solves in your context. If you can't clearly state the problem, you probably don't need the pattern. After applying a pattern, evaluate whether the added complexity is justified by the flexibility gained. Use the "Rule of Three" from Refactoring: don't introduce a pattern until you've seen the need for it at least three times. Let the code tell you what pattern it needs rather than imposing patterns on the code from outside.

Frameworks and Models

The 23 GoF Patterns Catalog

Organized by purpose (what the pattern does) and scope (class vs. object):

Creational Patterns — Abstract the instantiation process

Pattern Intent Use When
Abstract Factory Create families of related objects Multiple product families, platform independence
Builder Separate construction from representation Complex objects with many optional parts
Factory Method Let subclasses decide which class to instantiate Framework code that creates objects
Prototype Create objects by cloning a prototype Many similar objects, expensive creation
Singleton Ensure a class has only one instance Shared resources, configuration, registries

Structural Patterns — Compose classes and objects into larger structures

Pattern Intent Use When
Adapter Convert one interface to another Integrating incompatible interfaces
Bridge Separate abstraction from implementation Multiple dimensions of variation
Composite Treat individual and composite objects uniformly Tree structures (UI, file systems)
Decorator Add responsibilities dynamically Flexible alternative to subclassing
Facade Simplify a complex subsystem Reducing coupling to subsystem internals
Flyweight Share objects to support large numbers efficiently Many similar objects (characters, particles)
Proxy Control access to an object Lazy loading, access control, logging

Behavioral Patterns — Manage algorithms and object communication

Pattern Intent Use When
Chain of Responsibility Pass request along a chain of handlers Multiple potential handlers, determined at runtime
Command Encapsulate a request as an object Undo/redo, queuing, logging, macros
Iterator Access elements sequentially without exposing structure Traversing collections uniformly
Mediator Centralize complex communication between objects Many-to-many object interactions
Memento Capture and restore an object's state Undo, snapshots, checkpoints
Observer Notify dependents of state changes Event systems, MVC, reactive programming
State Change behavior when internal state changes Objects with state-dependent behavior
Strategy Encapsulate interchangeable algorithms Multiple algorithms for the same task
Template Method Define algorithm skeleton, defer steps to subclasses Frameworks with customizable steps
Visitor Add operations to objects without modifying them Operations across heterogeneous object structures

The Pattern Selection Decision Tree

What kind of problem are you solving?
│
├── HOW objects are created?
│   ├── Need families of related objects? → Abstract Factory
│   ├── Complex construction with many parts? → Builder
│   ├── Framework creates objects, app specifies type? → Factory Method
│   ├── Create by cloning? → Prototype
│   └── Exactly one instance needed? → Singleton
│
├── HOW objects are composed into structures?
│   ├── Incompatible interfaces need to work together? → Adapter
│   ├── Need to add capabilities without subclassing? → Decorator
│   ├── Tree structure with uniform treatment? → Composite
│   ├── Complex subsystem needs simple entry point? → Facade
│   ├── Need to control access to an object? → Proxy
│   └── Abstraction and implementation vary independently? → Bridge
│
└── HOW objects interact and distribute responsibility?
    ├── Notify many objects of changes? → Observer
    ├── Interchangeable algorithms? → Strategy
    ├── Behavior depends on state? → State
    ├── Encapsulate actions for undo/queue/log? → Command
    ├── Define algorithm skeleton with variable steps? → Template Method
    └── Add operations without modifying classes? → Visitor

The Two Fundamental Design Principles

Every GoF pattern is built on one or both of these principles:

Principle Meaning Violation Symptom Pattern Examples
Program to an interface, not an implementation Depend on abstractions, not concrete classes Changing one class forces changes in many others Strategy, Observer, Factory Method, Abstract Factory
Favor composition over inheritance Assemble behavior from parts rather than inheriting it Deep class hierarchies, rigid behavior, fragile base class problem Strategy, Decorator, State, Bridge, Composite

The Pattern Documentation Template

Each GoF pattern is documented using a consistent structure:

  1. Pattern Name — Concise name that becomes part of the design vocabulary
  2. Intent — What the pattern does in one sentence
  3. Also Known As — Alternative names
  4. Motivation — A scenario illustrating the problem and how the pattern solves it
  5. Applicability — When to use the pattern (the problem context)
  6. Structure — Class/object diagram showing participants and relationships
  7. Participants — The classes/objects and their responsibilities
  8. Collaborations — How participants interact
  9. Consequences — Trade-offs, benefits, and liabilities
  10. Implementation — Pitfalls, hints, and language-specific considerations
  11. Sample Code — Implementation in a concrete language
  12. Known Uses — Real-world examples of the pattern
  13. Related Patterns — Other patterns that are commonly used together or as alternatives

Key Quotes

"Design patterns capture solutions that have developed and evolved over time." — Gang of Four

"Program to an interface, not an implementation." — Gang of Four

"Favor object composition over class inheritance." — Gang of Four

"The key to maximizing reuse lies in anticipating new requirements and changes to existing requirements, and in designing your systems so that they can evolve accordingly." — Gang of Four

"Design patterns help you identify less-obvious abstractions and the objects that can capture them." — Gang of Four

Connections with Other Books

When to Use This Knowledge

Raw Markdown
# Design Patterns

> **One-sentence summary:** Recurring problems in software design have recurring solutions — design patterns are named, cataloged, and explained templates for solving common structural, behavioral, and creational challenges in object-oriented systems.

## Key Ideas

### 1. Patterns as a Shared Vocabulary for Design

The Gang of Four's (GoF) most lasting contribution may not be any individual pattern but the concept of a pattern language for software design. Before this book, experienced developers solved the same problems repeatedly but had no shared vocabulary for the solutions. One programmer's "wrapper class" was another's "adapter" and a third's "translator." The GoF gave these recurring solutions canonical names, precise descriptions, and standardized documentation — transforming implicit tribal knowledge into an explicit, teachable discipline.

The power of a shared vocabulary extends far beyond code. When a developer says "we should use an Observer here" or "this is a classic Strategy pattern," they communicate a complete design concept — intent, structure, participants, trade-offs — in a single phrase. Without the vocabulary, the same discussion would require drawing diagrams, writing pseudocode, and debating alternatives for minutes or hours. Patterns compress design conversations the same way technical jargon compresses any specialized discussion — they encode complex ideas into efficient tokens.

The GoF were explicit that patterns are not inventions but discoveries. They didn't create these solutions; they observed them recurring across different systems, different languages, and different domains. The Observer pattern exists in GUI frameworks, event systems, publish-subscribe architectures, and reactive programming — not because programmers read the GoF book, but because the problem (notifying multiple objects of state changes) recurs everywhere, and the solution converges on the same structure. Cataloging patterns is like cataloging species — you're documenting what already exists in the wild.

**Practical application:** Learn the names and intents of the 23 GoF patterns even before you master their implementation details. The vocabulary alone will transform your design discussions. When you encounter a design problem, ask: "Is this a known problem? Does a pattern exist for it?" Often, the answer is yes, and the pattern provides not just a solution but documentation of its trade-offs, consequences, and known variations.

### 2. Program to an Interface, Not an Implementation

The single most important design principle in the book — more fundamental than any individual pattern — is "program to an interface, not an implementation." This means that client code should depend on abstract interfaces (what an object can do) rather than concrete classes (how it does it). When code depends on interfaces, you can swap implementations without modifying the client — the system becomes flexible, testable, and extensible.

This principle is the foundation of almost every pattern in the catalog. Strategy works because the client depends on a strategy interface, not a specific algorithm. Observer works because subjects depend on an observer interface, not specific observers. Factory Method works because client code depends on a product interface, not a specific product class. The principle operates at every scale: within a single class (depend on abstractions of collaborators), between modules (communicate through interfaces), and across system boundaries (define contracts, not implementations).

The practical consequence is profound: designing with interfaces creates "seams" in the code — points where one implementation can be replaced by another. These seams are what make code testable (swap a real database for a mock), extensible (add new behaviors without modifying existing code), and maintainable (change how something works without changing the code that uses it). The absence of interfaces creates rigidity — every change ripples through the system because everything depends on everything else's concrete details.

**Practical application:** Whenever you create a class that will be used by other classes, ask: "Should clients depend on this concrete class, or on an interface that this class implements?" If the answer could ever be "there might be another way to do this," extract an interface. In practice, this means using dependency injection, defining clear contracts between modules, and resisting the temptation to call concrete methods directly when an abstraction would serve.

### 3. Favor Composition Over Inheritance

The GoF's second foundational principle is "favor object composition over class inheritance." Inheritance — the "is-a" relationship — is the first tool most object-oriented programmers reach for when they want to reuse behavior. But inheritance creates tight coupling between parent and child classes, makes the system brittle (changes to the parent ripple to all children), and locks the structure at compile time. Composition — the "has-a" relationship — assembles behavior from smaller, interchangeable parts and can be reconfigured at runtime.

The Strategy pattern exemplifies this principle perfectly. Instead of creating a `SortableList` subclass for each sorting algorithm (`BubbleSortList`, `QuickSortList`, `MergeSortList`), you compose a `List` with a `SortStrategy` object that can be swapped dynamically. Instead of inheriting behavior, the list delegates to a composable component. The result is fewer classes, more flexibility, and the ability to change behavior at runtime rather than being locked into a compile-time hierarchy.

The GoF were not anti-inheritance — they used it extensively in their patterns. Their argument was that inheritance should be reserved for expressing genuine type relationships ("a Square is a Shape") and should not be used as a mechanism for code reuse. When you inherit to reuse behavior, you create a permanent, structural dependency on the parent class's implementation. When you compose to reuse behavior, you create a flexible, pluggable relationship that can be changed independently. The distinction is between reusing a type (inheritance) and reusing a capability (composition).

**Practical application:** When you find yourself creating a class hierarchy primarily to share behavior, stop and ask: "Could I achieve this with composition instead?" If class B needs some of class A's behavior but isn't truly a subtype of A, extract the shared behavior into a separate object and compose both A and B with it. The Decorator, Strategy, and State patterns are all composition-based alternatives to what would otherwise be deep inheritance hierarchies.

### 4. Creational Patterns: Controlling How Objects Are Born

The GoF catalog divides patterns into three categories. Creational patterns address how objects are instantiated — abstracting the creation process so that systems can be independent of how their objects are created, composed, and represented. The five creational patterns are Abstract Factory, Builder, Factory Method, Prototype, and Singleton.

The central problem creational patterns solve is rigid coupling to concrete classes. When code says `new ConcreteProduct()`, it is permanently bound to that specific class. If the product needs to change — different implementation for different platforms, different configuration for different environments, different variants for different customers — every `new` call must be found and modified. Creational patterns encapsulate the creation logic so that the rest of the system remains oblivious to which specific classes are being instantiated.

Factory Method and Abstract Factory are the most widely used. Factory Method defines an interface for creating an object but lets subclasses decide which class to instantiate — the framework calls the factory method, and the application supplies the specific factory. Abstract Factory provides an interface for creating families of related objects without specifying their concrete classes — useful when a system must work with multiple product families (e.g., UI toolkits for different operating systems). Builder separates the construction of a complex object from its representation, enabling the same construction process to create different representations.

**Practical application:** Use Factory Method when your code creates objects but the specific type should be determined by subclasses or configuration. Use Builder when constructing complex objects with many optional parameters — it produces more readable code than constructors with many arguments. Use Singleton sparingly and with awareness of its costs (global state, testing difficulty, hidden dependencies). Default to the simplest creation pattern that eliminates the concrete coupling.

### 5. Structural Patterns: Organizing Relationships Between Objects

Structural patterns describe how classes and objects are composed to form larger structures. They use inheritance and composition to create new functionality from existing components. The seven structural patterns are Adapter, Bridge, Composite, Decorator, Facade, Flyweight, and Proxy.

Adapter and Facade are the most immediately practical. Adapter converts the interface of one class into another interface that clients expect — it's the software equivalent of a power plug adapter. When you integrate a third-party library whose API doesn't match your system's expectations, you write an Adapter. Facade provides a simplified interface to a complex subsystem — a single entry point that hides the subsystem's internal complexity. When your payment processing involves six different services, a `PaymentFacade` with a single `processPayment()` method shields the rest of the system from that complexity.

Decorator and Composite are the most structurally elegant. Decorator attaches additional responsibilities to an object dynamically, providing a flexible alternative to subclassing. Java's I/O streams are the classic example: `new BufferedReader(new InputStreamReader(new FileInputStream("file.txt")))` — each wrapper adds a capability without modifying the underlying stream. Composite lets you treat individual objects and compositions of objects uniformly through a common interface — the file system (files and directories) and UI component trees are canonical examples.

**Practical application:** When integrating external systems or legacy code, reach for Adapter to translate interfaces rather than modifying either side. When a subsystem's complexity is leaking into client code, introduce a Facade. When you need to add capabilities to objects without creating an explosion of subclasses, use Decorator. When dealing with tree structures (UI components, file systems, organizational hierarchies), consider Composite.

### 6. Behavioral Patterns: Managing Algorithms and Responsibilities

Behavioral patterns are concerned with algorithms, assignment of responsibilities between objects, and patterns of communication. They describe not just objects and classes but the patterns of interaction between them. The eleven behavioral patterns include Chain of Responsibility, Command, Iterator, Mediator, Memento, Observer, State, Strategy, Template Method, and Visitor.

Observer and Strategy are arguably the two most influential patterns in modern software. Observer defines a one-to-many dependency between objects so that when one object changes state, all its dependents are notified automatically. It's the foundation of event-driven programming, reactive systems, the Model-View-Controller architecture, and every publish-subscribe system. Strategy defines a family of algorithms, encapsulates each one, and makes them interchangeable — it lets the algorithm vary independently from clients that use it. Together, Observer and Strategy enable the kind of loosely-coupled, pluggable, event-driven architectures that dominate modern software.

State and Command are underappreciated but extremely powerful. State allows an object to alter its behavior when its internal state changes — the object appears to change its class. Instead of complex if-else chains checking the current state, each state is a separate object with its own behavior. Command encapsulates a request as an object, allowing you to parameterize clients with different requests, queue or log requests, and support undoable operations. Every undo/redo system, every task queue, and every macro-recording system is built on the Command pattern.

**Practical application:** When you find yourself writing complex conditional logic that checks the "type" or "state" of an object to determine behavior, consider Strategy (for interchangeable algorithms) or State (for state-dependent behavior). When you need to decouple event sources from event handlers, use Observer. When you need to encapsulate actions as objects (for queuing, logging, or undo), use Command. The behavioral patterns are the most frequently applicable in daily coding.

### 7. Patterns Are Not Recipes: The Importance of Context and Judgment

The GoF were emphatic that patterns are not cookie-cutter solutions to be applied mechanically. Each pattern has a specific context (the problem it solves), forces (the competing concerns it balances), and consequences (the trade-offs it introduces). Applying a pattern in the wrong context — or applying too many patterns — is worse than applying none. The book explicitly warns against "pattern-itis," the tendency of developers who've just learned patterns to see them everywhere and apply them unnecessarily.

A pattern introduces structural complexity — additional classes, interfaces, and indirection. This complexity is justified only when it solves a real problem. Using a Strategy pattern where a simple if-else would suffice adds classes, interfaces, and cognitive overhead for zero benefit. Using an Abstract Factory when there's only one product family adds a layer of abstraction that no one will ever need. The art of design patterns is not knowing the patterns but knowing when to use them — and equally importantly, when not to.

The GoF also recognized that patterns exist at different scales and levels of abstraction. The 23 patterns in the catalog are mid-level design patterns — larger than idioms (language-specific coding conventions) but smaller than architectural patterns (MVC, microservices, event-driven architecture). Understanding this hierarchy prevents the mistake of trying to solve architectural problems with design patterns or language-level problems with architectural patterns. Each level has its own appropriate tools.

**Practical application:** Before applying a pattern, articulate the specific problem it solves in your context. If you can't clearly state the problem, you probably don't need the pattern. After applying a pattern, evaluate whether the added complexity is justified by the flexibility gained. Use the "Rule of Three" from Refactoring: don't introduce a pattern until you've seen the need for it at least three times. Let the code tell you what pattern it needs rather than imposing patterns on the code from outside.

## Frameworks and Models

### The 23 GoF Patterns Catalog

Organized by purpose (what the pattern does) and scope (class vs. object):

**Creational Patterns** — Abstract the instantiation process

| Pattern | Intent | Use When |
|---------|--------|----------|
| **Abstract Factory** | Create families of related objects | Multiple product families, platform independence |
| **Builder** | Separate construction from representation | Complex objects with many optional parts |
| **Factory Method** | Let subclasses decide which class to instantiate | Framework code that creates objects |
| **Prototype** | Create objects by cloning a prototype | Many similar objects, expensive creation |
| **Singleton** | Ensure a class has only one instance | Shared resources, configuration, registries |

**Structural Patterns** — Compose classes and objects into larger structures

| Pattern | Intent | Use When |
|---------|--------|----------|
| **Adapter** | Convert one interface to another | Integrating incompatible interfaces |
| **Bridge** | Separate abstraction from implementation | Multiple dimensions of variation |
| **Composite** | Treat individual and composite objects uniformly | Tree structures (UI, file systems) |
| **Decorator** | Add responsibilities dynamically | Flexible alternative to subclassing |
| **Facade** | Simplify a complex subsystem | Reducing coupling to subsystem internals |
| **Flyweight** | Share objects to support large numbers efficiently | Many similar objects (characters, particles) |
| **Proxy** | Control access to an object | Lazy loading, access control, logging |

**Behavioral Patterns** — Manage algorithms and object communication

| Pattern | Intent | Use When |
|---------|--------|----------|
| **Chain of Responsibility** | Pass request along a chain of handlers | Multiple potential handlers, determined at runtime |
| **Command** | Encapsulate a request as an object | Undo/redo, queuing, logging, macros |
| **Iterator** | Access elements sequentially without exposing structure | Traversing collections uniformly |
| **Mediator** | Centralize complex communication between objects | Many-to-many object interactions |
| **Memento** | Capture and restore an object's state | Undo, snapshots, checkpoints |
| **Observer** | Notify dependents of state changes | Event systems, MVC, reactive programming |
| **State** | Change behavior when internal state changes | Objects with state-dependent behavior |
| **Strategy** | Encapsulate interchangeable algorithms | Multiple algorithms for the same task |
| **Template Method** | Define algorithm skeleton, defer steps to subclasses | Frameworks with customizable steps |
| **Visitor** | Add operations to objects without modifying them | Operations across heterogeneous object structures |

### The Pattern Selection Decision Tree

```
What kind of problem are you solving?
│
├── HOW objects are created?
│   ├── Need families of related objects? → Abstract Factory
│   ├── Complex construction with many parts? → Builder
│   ├── Framework creates objects, app specifies type? → Factory Method
│   ├── Create by cloning? → Prototype
│   └── Exactly one instance needed? → Singleton
│
├── HOW objects are composed into structures?
│   ├── Incompatible interfaces need to work together? → Adapter
│   ├── Need to add capabilities without subclassing? → Decorator
│   ├── Tree structure with uniform treatment? → Composite
│   ├── Complex subsystem needs simple entry point? → Facade
│   ├── Need to control access to an object? → Proxy
│   └── Abstraction and implementation vary independently? → Bridge
│
└── HOW objects interact and distribute responsibility?
    ├── Notify many objects of changes? → Observer
    ├── Interchangeable algorithms? → Strategy
    ├── Behavior depends on state? → State
    ├── Encapsulate actions for undo/queue/log? → Command
    ├── Define algorithm skeleton with variable steps? → Template Method
    └── Add operations without modifying classes? → Visitor
```

### The Two Fundamental Design Principles

Every GoF pattern is built on one or both of these principles:

| Principle | Meaning | Violation Symptom | Pattern Examples |
|-----------|---------|-------------------|------------------|
| **Program to an interface, not an implementation** | Depend on abstractions, not concrete classes | Changing one class forces changes in many others | Strategy, Observer, Factory Method, Abstract Factory |
| **Favor composition over inheritance** | Assemble behavior from parts rather than inheriting it | Deep class hierarchies, rigid behavior, fragile base class problem | Strategy, Decorator, State, Bridge, Composite |

### The Pattern Documentation Template

Each GoF pattern is documented using a consistent structure:

1. **Pattern Name** — Concise name that becomes part of the design vocabulary
2. **Intent** — What the pattern does in one sentence
3. **Also Known As** — Alternative names
4. **Motivation** — A scenario illustrating the problem and how the pattern solves it
5. **Applicability** — When to use the pattern (the problem context)
6. **Structure** — Class/object diagram showing participants and relationships
7. **Participants** — The classes/objects and their responsibilities
8. **Collaborations** — How participants interact
9. **Consequences** — Trade-offs, benefits, and liabilities
10. **Implementation** — Pitfalls, hints, and language-specific considerations
11. **Sample Code** — Implementation in a concrete language
12. **Known Uses** — Real-world examples of the pattern
13. **Related Patterns** — Other patterns that are commonly used together or as alternatives

## Key Quotes

> "Design patterns capture solutions that have developed and evolved over time." — Gang of Four

> "Program to an interface, not an implementation." — Gang of Four

> "Favor object composition over class inheritance." — Gang of Four

> "The key to maximizing reuse lies in anticipating new requirements and changes to existing requirements, and in designing your systems so that they can evolve accordingly." — Gang of Four

> "Design patterns help you identify less-obvious abstractions and the objects that can capture them." — Gang of Four

## Connections with Other Books

- [[clean-code]]: Robert C. Martin's principles of clean code — small functions, single responsibility, clear naming — are the code-level implementation of the GoF's design-level principles. Clean Code tells you how to write individual functions and classes well; Design Patterns tells you how to compose those classes into larger structures. Together they cover micro and macro design.

- [[refactoring]]: Fowler's refactoring catalog provides the mechanics for evolving code toward patterns. Many refactorings — Extract Class, Replace Conditional with Polymorphism, Replace Type Code with State/Strategy — are literally the steps for introducing GoF patterns into existing code. Refactoring is how you get from messy code to patterned code safely and incrementally.

- [[the-pragmatic-programmer]]: Hunt and Thomas's principle of "DRY" (Don't Repeat Yourself) and "orthogonality" (keep things independent) are the practical motivations for many GoF patterns. Strategy eliminates duplicated conditional logic. Observer eliminates coupling between event sources and handlers. Facade reduces the surface area of dependencies. The Pragmatic Programmer provides the philosophy; Design Patterns provides the structural solutions.

- [[antifragile]]: Taleb's principle of building systems that benefit from change connects to the GoF's emphasis on designing for change. Patterns like Strategy, Observer, and Factory Method create "seams" in the code where new behavior can be inserted without modifying existing code — the Open/Closed Principle. Well-patterned code is antifragile to requirements changes: each change makes the pattern investment more valuable.

- [[the-lean-startup]]: Ries's emphasis on building for change, not for permanence, aligns with the GoF's evolutionary design approach. Patterns provide the structural flexibility to pivot — Strategy lets you swap algorithms, Factory Method lets you swap implementations, Observer lets you add new event handlers. Building with patterns is building for the pivot.

- [[deep-work]]: Newport's argument for focused, concentrated work applies directly to design pattern application. Understanding when and how to apply patterns requires the kind of sustained, deep analytical thinking that shallow work environments prevent. Pattern selection is a System 2 activity that demands concentration and deliberation.

## When to Use This Knowledge

- When the user asks about **software design, architecture, or code structure** — the GoF catalog provides the canonical vocabulary and solutions for recurring design problems.
- When someone is facing a **specific design challenge** (decoupling components, managing state transitions, creating object families) — the pattern selection decision tree helps identify the right pattern.
- When the context involves **code review or design review** — pattern names enable precise, efficient communication about structural decisions.
- When the user is dealing with **rigid, hard-to-change code** — many patterns (Strategy, Observer, Factory Method) are specifically designed to introduce flexibility and reduce coupling.
- When the discussion involves **object-oriented programming principles** — the two fundamental principles (interface over implementation, composition over inheritance) underlie all 23 patterns.
- When someone asks about **when not to use patterns** — the "patterns are not recipes" concept helps prevent over-engineering and pattern-itis.
- When the user is learning **software engineering fundamentals** — Design Patterns is one of the foundational texts that every professional developer should understand.
- When the context involves **framework design or library API design** — patterns like Template Method, Factory Method, and Strategy are specifically designed for creating extensible frameworks.