GoF Design Patterns
(AI summarized reading)
Published:
Design Patterns: Elements of Reusable Object-Oriented Software
Authors: Erich Gamma, Richard Helm, Ralph Johnson, John Vlissides (the “Gang of Four”) Published: 1994 Source: https://www.javier8a.com/itc/bd1/articulo.pdf
Overview
A foundational text in software engineering that catalogs 23 recurring solutions to common object-oriented design problems. The book argues that experienced designers don’t solve problems from scratch — they reuse patterns that have worked before. A design pattern names, abstracts, and identifies the key aspects of a common design structure that makes it useful for creating reusable OO design.
Core Concepts
What is a Design Pattern?
Each pattern has four essential elements:
- Pattern name — a handle to describe a design problem and its solution in one or two words
- Problem — describes when to apply the pattern and its context
- Solution — describes the elements, relationships, responsibilities, and collaborations
- Consequences — trade-offs and results of applying the pattern (space, time, flexibility)
Key OO Design Principles
- Program to an interface, not an implementation — depend on abstract types, not concrete classes
- Favor object composition over class inheritance — keeps each class encapsulated and focused on one task
- Find what varies and encapsulate it — identifies what should be hidden behind an abstraction
How Patterns Relate to Design
- Patterns help you design flexible, reusable OO software
- They capture solutions that have been developed and evolved over time
- Knowing patterns makes it easier to communicate design decisions
The 23 Patterns
Organized into three categories based on purpose:
🏗️ Creational Patterns
Deal with object creation mechanisms, decoupling the system from how its objects are created.
Abstract Factory
- Intent: Provide an interface for creating families of related or dependent objects without specifying their concrete classes
- Use when: A system must be independent of how its products are created; you want to enforce constraints among related products
- Key idea: Factory methods grouped into a factory object; swap the whole factory to change a family of products
- Example: UI toolkit that must work on multiple platforms (Windows / macOS widgets)
Builder
- Intent: Separate the construction of a complex object from its representation so the same construction process can create different representations
- Use when: The algorithm for creating a complex object should be independent of the parts that make up the object
- Key idea: A
Directororchestrates aBuilder; the Builder assembles the product step by step - Example: Document converters (RTF → PDF, RTF → TeX), meal builders
Factory Method
- Intent: Define an interface for creating an object, but let subclasses decide which class to instantiate. Lets a class defer instantiation to subclasses
- Use when: A class can’t anticipate the class of objects it must create; subclasses should specify the objects they create
- Key idea: “Virtual constructor” — override a creator method to return a different product
- Example:
Application.createDocument()deferred toDrawingApplication,SpreadsheetApplication
Prototype
- Intent: Specify the kinds of objects to create using a prototypical instance, and create new objects by copying (cloning) this prototype
- Use when: Classes to instantiate are specified at run-time; the cost of creating a new object via
newis high - Key idea: Clone an existing object instead of constructing from scratch
- Example: Graphical editors where users copy-paste shapes
Singleton
- Intent: Ensure a class only has one instance, and provide a global point of access to it
- Use when: Exactly one object is needed to coordinate actions across the system
- Key idea: Hide the constructor; expose a static
getInstance()method - Caution: Overuse leads to tight coupling and difficulty testing; prefer dependency injection where possible
- Example: Logger, configuration manager, thread pool
🔧 Structural Patterns
Deal with how classes and objects are composed to form larger structures.
Adapter
- Intent: Convert the interface of a class into another interface clients expect. Lets incompatible interfaces work together
- Use when: You want to use an existing class but its interface doesn’t match what you need
- Two forms: Class adapter (multiple inheritance) and Object adapter (composition)
- Example: Adapting a third-party graphics library’s
LegacyRectangleto yourShapeinterface
Bridge
- Intent: Decouple an abstraction from its implementation so that the two can vary independently
- Use when: You want to avoid a permanent binding between abstraction and implementation; both should be extensible via subclassing
- Key idea: The “abstraction” holds a reference to an “implementor” rather than inheriting from one
- Example: Window system (Window abstraction) separated from platform implementation (XWindow, PMWindow)
Composite
- Intent: Compose objects into tree structures to represent part-whole hierarchies. Lets clients treat individual objects and compositions uniformly
- Use when: You want clients to be able to ignore the difference between compositions of objects and individual objects
- Key idea: Leaf and Composite share the same Component interface
- Example: File system (files and directories), UI widget trees, document structure (paragraphs inside sections inside chapters)
Decorator
- Intent: Attach additional responsibilities to an object dynamically. Provides a flexible alternative to subclassing for extending functionality
- Use when: You want to add responsibilities without subclassing; responsibilities can be withdrawn; extension by subclassing is impractical due to a large number of combinations
- Key idea: Wrap the component in a decorator that delegates to it and adds behavior before/after
- Example: Streams with scrollbars and borders; Java’s
BufferedInputStream
Facade
- Intent: Provide a unified interface to a set of interfaces in a subsystem. Defines a higher-level interface that makes the subsystem easier to use
- Use when: You want to provide a simple interface to a complex subsystem; there are many dependencies between clients and implementation classes
- Key idea: Wrap a complex API in a simpler one; doesn’t prevent access to the underlying system
- Example: Compiler façade (
Compiler.compile()) hiding Scanner, Parser, ProgramNode, etc.
Flyweight
- Intent: Use sharing to support large numbers of fine-grained objects efficiently
- Use when: An application uses a large number of objects that have much state in common; storage costs are high due to quantity
- Key idea: Split state into intrinsic (shared, stored in flyweight) and extrinsic (context-dependent, passed by client)
- Example: Characters in a text editor, particles in a game engine
Proxy
- Intent: Provide a surrogate or placeholder for another object to control access to it
- Use when: You need a smarter or more versatile reference to an object than a plain pointer
- Types: Remote proxy, virtual proxy (lazy loading), protection proxy, smart reference
- Example: Image proxy that loads the full image only when
draw()is called
🔄 Behavioral Patterns
Deal with communication, responsibility, and algorithms between objects.
Chain of Responsibility
- Intent: Avoid coupling the sender of a request to its receiver by giving more than one object a chance to handle it. Chain the receiving objects and pass the request along until one handles it
- Use when: More than one object may handle a request; the handler isn’t known a priori
- Example: Help system, event handling in UI frameworks, middleware pipelines
Command
- Intent: Encapsulate a request as an object, letting you parameterize clients with different requests, queue or log requests, and support undoable operations
- Use when: You need to parameterize actions, support undo, implement transactions
- Key idea: Command object has an
execute()method; anInvokerstores and calls commands - Example: Menu items, macro recording, job queues, transactional systems
Interpreter
- Intent: Given a language, define a representation for its grammar along with an interpreter that uses the representation to interpret sentences in the language
- Use when: The grammar is simple and efficiency is not critical
- Example: Regular expressions, SQL parsing, scripting languages
Iterator
- Intent: Provide a way to sequentially access elements of an aggregate object without exposing its underlying representation
- Use when: You need a standard way to traverse different types of collections
- Key idea: Separates traversal logic from the collection; supports multiple concurrent traversals
- Example: Java’s
Iterator, C++’s STL iterators
Mediator
- Intent: Define an object that encapsulates how a set of objects interact. Promotes loose coupling by keeping objects from referring to each other explicitly
- Use when: A set of objects communicate in well-defined but complex ways; reusing objects is difficult because they refer to many others
- Example: Chat room (all messages routed through a central server), UI dialog boxes coordinating widget interactions
Memento
- Intent: Without violating encapsulation, capture and externalize an object’s internal state so the object can be restored to this state later
- Use when: A snapshot of an object’s state must be saved so it can be restored later (undo)
- Key idea: A
CaretakerstoresMementoobjects; only theOriginatorcan access the memento’s internals - Example: Undo in text editors, save-states in games
Observer
- Intent: Define a one-to-many dependency between objects so that when one object changes state, all its dependents are notified and updated automatically
- Use when: A change in one object requires changing others, and you don’t know how many objects need to change
- Key idea: Subject maintains a list of observers and notifies them on state change
- Example: MVC (Model notifies Views), event listeners, publish-subscribe systems
State
- Intent: Allow an object to alter its behavior when its internal state changes. The object will appear to change its class
- Use when: An object’s behavior depends on its state and must change at run-time; operations have large multipart conditionals on state
- Key idea: Delegate state-specific behavior to State objects; transition by replacing the current state
- Example: TCP connection states (Established, Listen, Closed), vending machine, order processing workflow
Strategy
- Intent: Define a family of algorithms, encapsulate each one, and make them interchangeable. Lets the algorithm vary independently from clients that use it
- Use when: Many related classes differ only in behavior; you need different variants of an algorithm
- Key idea: Extract the algorithm into a Strategy object; the Context delegates to it
- Example: Sorting algorithms, compression strategies, payment methods, text layout
Template Method
- Intent: Define the skeleton of an algorithm in an operation, deferring some steps to subclasses. Lets subclasses redefine certain steps without changing the algorithm’s structure
- Use when: You want to implement the invariant parts of an algorithm once and let subclasses fill in the variable behavior
- Key idea: Inversion of control (“the Hollywood Principle: don’t call us, we’ll call you”)
- Example: Application framework lifecycle hooks, data parsing pipelines with fixed steps
Visitor
- Intent: Represent an operation to be performed on the elements of an object structure. Lets you define a new operation without changing the classes of the elements on which it operates
- Use when: An object structure contains many classes with differing interfaces, and you want to perform operations that depend on their concrete classes
- Key idea: Double dispatch — the element accepts a visitor, which then dispatches on its own type
- Tradeoff: Adding new element types is hard (requires updating all visitors); adding new operations is easy
- Example: AST traversals in compilers, document rendering in different formats
Pattern Relationships & Cheat Sheet
| Pattern | Category | Core Idea |
|---|---|---|
| Abstract Factory | Creational | Family of related objects |
| Builder | Creational | Complex object step-by-step |
| Factory Method | Creational | Subclass decides what to create |
| Prototype | Creational | Clone existing instance |
| Singleton | Creational | One instance, global access |
| Adapter | Structural | Interface translation |
| Bridge | Structural | Abstraction ↔ Implementation split |
| Composite | Structural | Tree of objects, uniform interface |
| Decorator | Structural | Add behavior by wrapping |
| Facade | Structural | Simple interface to complex subsystem |
| Flyweight | Structural | Share fine-grained objects |
| Proxy | Structural | Controlled object access |
| Chain of Responsibility | Behavioral | Pass request along a chain |
| Command | Behavioral | Encapsulate request as object |
| Interpreter | Behavioral | Language grammar as objects |
| Iterator | Behavioral | Traverse collection uniformly |
| Mediator | Behavioral | Central communication hub |
| Memento | Behavioral | Snapshot for undo |
| Observer | Behavioral | Notify dependents on change |
| State | Behavioral | Behavior changes with state |
| Strategy | Behavioral | Swappable algorithms |
| Template Method | Behavioral | Skeleton algorithm, subclass fills in |
| Visitor | Behavioral | Add operations without modifying classes |
Key Takeaways & Notes
- Inheritance vs. Composition: The book strongly advocates for composition over inheritance. Inheritance is a white-box relationship (exposes parent internals); composition is black-box (only the interface is visible). Prefer it for flexibility.
- Interfaces matter more than implementations: Design to abstractions. If you depend on concrete classes, you’re coupled to their implementations.
- Patterns work together: Many patterns collaborate naturally (e.g., Abstract Factory + Singleton; Composite + Iterator + Visitor; Strategy + Template Method).
- Don’t over-pattern: Patterns add indirection and complexity. Only apply them when flexibility or reuse justifies it.
- Common design challenges patterns address:
- Finding appropriate objects
- Determining object granularity
- Specifying object interfaces
- Specifying object implementations
- Designing for change
