Introduction
Design patterns systematically name, motivate, and explain recurring object-oriented design solutions to build flexible and maintainable software.
You face a difficult challenge when you try to build object-oriented software that is reusable, robust, and extensible. You often find yourself repeating successful design experiences or struggling with designs that become hard to change. Finding good object-oriented design is hard, and finding it that works quickly is even harder. You need solutions that last, but expert designers rarely invent every solution from scratch. They reuse solutions that have worked for them in the past.
This book captures these proven solutions and organizes them into a compact catalog. You will learn how to build systems that handle change gracefully without requiring major redesigns. The text guides you through the foundational concepts of object-oriented design, demonstrates a complete document editor case study, and details the specific creational, structural, and behavioral patterns.
- How do you find the right objects for your design?
- What determines the granularity of your object decomposition?
- How do you specify object interfaces without coupling classes?
- How do you design for reuse while keeping code extensible?
1. Introduction to Design Patterns
A design pattern systematically names, motivates, and explains a general design that addresses a recurring design problem in object-oriented systems. It describes the problem, the solution, when to apply the solution, its consequences, implementation hints, and examples.
Design patterns provide a common design vocabulary and consist of four essential elements: name, problem, solution, and consequences.
Every design pattern consists of four essential elements:
- a pattern name;
- a problem description;
- an abstract solution;
- the resulting consequences.
The pattern name creates a design vocabulary to raise abstraction levels. The problem explains the context and when to apply the pattern. The solution outlines an abstract arrangement of classes and objects rather than a concrete implementation. The consequences address trade-offs regarding space, time, flexibility, extensibility, and portability.
The design pattern catalog contains 23 design patterns in the catalog. The patterns are classified by purpose into creational, structural, and behavioral categories, and by scope into class patterns or object patterns depending on whether they apply primarily to static inheritance relationships or dynamic run-time object associations.
Design patterns provide a standard vocabulary that raises the abstraction level of communication among software designers. Studies of expert programmers show that knowledge is organized around conceptual structures like algorithms, data structures, and idioms. Design patterns name and catalog these structures, allowing teams to discuss architectures at a higher level than raw programming languages or design notations.
You are invited to actively use design patterns, provide critical feedback on trade-offs, and document your own practical patterns. Look for patterns you use, write them down, make them part of your documentation, and show them to other people.
- Use patterns and look for other patterns that fit the way you design.
- Develop your vocabulary of patterns and use it when you talk, think, and write about designs.
- Be a critical consumer and contact authors if you spot a problem or need more explanation.
- Look for patterns you use, write them down, make them part of your documentation, and show them to other people.
2. How Design Patterns Solve Problems
You need to reduce implementation dependencies between subsystems to build flexible systems. Variables should not be declared as instances of particular concrete classes. Instead, you should commit only to an interface defined by an abstract class.
You should program to an interface and favor object composition over class inheritance to ensure runtime flexibility.
Class inheritance provides white-box reuse by exposing parent class internals. This approach breaks encapsulation and creates rigid compile-time dependencies. Object composition provides black-box reuse through well-defined interfaces. You can assemble and replace objects dynamically at run-time without breaking encapsulation.
Delegation makes object composition as powerful for reuse as inheritance by letting a receiving object forward operations to a delegate. To allow the delegated operation to refer to the receiver, the receiving object passes itself to the delegate. This mechanism permits composing and changing behaviors at run-time.
A Window class delegates its Area operation to a Rectangle instance instead of inheriting from a Rectangle class.
Toolkits provide general-purpose subroutine libraries without imposing a specific application architecture. Frameworks define the overall architecture, partitioning, and collaboration rules. This creates an inversion of control where the framework calls your application code.
Applying a design pattern effectively requires following a systematic procedure:
- Read the pattern once through for an overview, paying attention to Applicability and Consequences.
- Study the Structure, Participants, and Collaborations sections to understand relationships.
- Look at the Sample Code section to see a concrete example in code.
- Choose names for pattern participants that are meaningful in the application context.
- Define the classes, declaring interfaces, inheritance relationships, and instance variables.
- Define application-specific names for operations using responsibilities and collaborations as a guide.
- Implement the operations to carry out the responsibilities and collaborations using implementation hints.
3. A Case Study: Designing Lexi
Building a document editor demonstrates how multiple design patterns interact to solve complex recursive composition and formatting problems.
Lexi represents every character, line, column, and graphical element as an object derived from a common Glyph abstract class. This allows the application to handle text and graphics uniformly with respect to drawing, formatting, and embedding without needing special code for single elements versus groups.
In Lexi, arranging characters and graphics left to right forms a line, multiple lines form a column, and multiple columns form a page.
By separating the compositor from the composition, developers can swap out different linebreaking algorithms without touching the glyph classes, balancing formatting speed and visual quality.
Lexi uses SimpleCompositor for quick passes and TeXCompositor to implement the full TEX algorithm for better visual color distribution.
Instead of creating a combinatorial explosion of subclasses like BorderedScrollableComposition, an embellishment class wraps a single component while matching its interface, forwarding requests while optionally performing additional tasks before or after delegation.
Lexi wraps its text editing composition inside a Scroller instance and then wraps that inside a Border instance.
Object creation can be abstracted to support multiple look-and-feel standards and allow switching them at run-time without hard-coding constructor calls. An abstract factory interface defines methods for creating widget families. Concrete factories implement these methods to instantiate widgets tailored to specific platforms, entirely removing platform-specific class names from client code.
MotifFactory and PMFactory manufacture platform-specific widget glyphs like scroll bars and buttons when requested by the application.
A system can run across incompatible window environments by separating the high-level window abstraction from low-level window system implementations using a bridge pattern. The Window class provides a unified interface for application programmers, while the WindowImp hierarchy hides window system-dependent code. This avoids maintaining multiple parallel class hierarchies for every platform.
XWindowImp handles rectangle drawing using XDrawRectangle coordinates, whereas PMWindowImp uses Presentation Manager path vertices.
User requests and operations can be decoupled from user interface widgets and made undoable by encapsulating them in command objects. A Command abstract class defines an Execute method along with reversal support. A command history list tracks executed commands, enabling multi-level undo and redo by moving a present pointer back and forth.
Lexi uses FontCommand to store text ranges and original fonts so that Execute applies the font change and Unexecute restores the original formatting.
Object structures can be traversed without exposing their internal storage representations by encapsulating access and traversal logic in iterator objects. Instead of relying on integer indexes or hard-coding list versus array checks into glyph classes, iterators maintain their own traversal state and provide uniform access methods like First, Next, and IsDone.
PreorderIterator and ListIterator allow traversing document element structures without knowing whether children are stored in arrays or linked lists.
Analytical capabilities can be added to an open-ended number of features without modifying the core object classes by using the visitor pattern. Visitors travel across an object structure during a traversal to perform specific analysis tasks. By combining iterators and visitors, operations like spelling checking and hyphenation can be implemented in separate classes without bloating the Glyph interface.
SpellingCheckingVisitor implements VisitCharacter to accumulate text into a buffer and check words against a spelling dictionary.
4. Creational Patterns
Creational design patterns abstract the object instantiation process, making systems independent of how objects are created, composed, and represented.
As systems evolve to depend on object composition rather than class inheritance, creational patterns encapsulate knowledge about concrete classes. You hide how instances are created and assembled. This flexibility determines what gets created, who creates it, and when. You can configure systems either statically or dynamically.
Building a maze for a computer game uses Room, Door, and Wall components.
Abstract Factory
The Abstract Factory pattern provides an interface for creating families of related or dependent objects without specifying their concrete classes. You use a concrete factory instance created at run-time to isolate clients from implementation classes. While this makes exchanging product families easy and enforces product consistency, supporting new kinds of products remains difficult. The interface fixes the exact set of creatable products.
A user interface toolkit supports multiple look-and-feel standards like Motif and Presentation Manager widgets.
Builder
The Builder pattern separates the construction of a complex object from its representation so that the same construction process can create different representations. You gain finer control over the internal structure and allow the same algorithm to generate multiple variants.
An RTF reader acts as a director that parses a document and notifies a text converter builder whenever a token is recognized.
- An RTF document reader converts documents into plain ASCII text.
- The same reader generates TeX representation.
- You can also produce editable text widgets.
Factory Method
The Factory Method pattern defines an interface for creating an object, but lets subclasses decide which class to instantiate by deferring instantiation. You remove application-specific classes from framework code by letting subclasses override a factory method. This provides hooks for subclasses and connects parallel class hierarchies.
An Application framework manages and creates application-specific documents when you select Open or New from a menu.
Prototype
The Prototype pattern specifies the kinds of objects to create using a prototypical instance and creates new objects by copying this prototype. You create new objects by asking a prototype to clone itself, which avoids building a class hierarchy of factories. You can add or remove products at run-time. The primary liability is that each prototype subclass must correctly implement a deep-copy Clone operation.
A music score editor uses tools that create quarter notes or half notes by cloning prototypical music objects.
Singleton
The Singleton pattern ensures a class has only one instance and provides a global point of access to it. You make the class responsible for tracking its sole instance by hiding its constructor and providing a static instance operation with lazy initialization. This avoids polluting the name space with global variables while permitting controlled access and subclassing refinement.
A printer spooler, window manager, or a single A/D converter in a digital filter operates as a single instance.
5. Structural Patterns
Structural patterns combine classes and objects into larger structures while keeping them flexible, efficient, and decoupled.
You can use the Adapter pattern when you need to convert the incompatible interface of an existing class into another interface that clients expect. This allows unrelated classes to work together without modifying their source code.
- Identify an existing class whose interface does not match the one you need.
- Determine whether to use a class adapter via private inheritance or an object adapter via object composition.
- Implement the Target interface in the adapter and forward requests to the Adaptee while adding any missing functionality.
A drawing editor uses the Shape interface for graphical objects, but wants to reuse an off-the-shelf TextView class for text editing which has an incompatible interface. A TextShape adapter wraps or inherits from TextView to translate BoundingBox requests into GetExtent requests.
The Bridge pattern decouples an abstraction from its implementation so that both can be varied, extended, and compiled independently.
- Separate an abstraction and its implementation into two distinct class hierarchies.
- Maintain a reference to an Implementor object inside the Abstraction class.
- Implement high-level abstraction operations in terms of primitive operations defined in the Implementor interface.
A user interface toolkit implements a portable Window abstraction that works on both the X Window System and IBM Presentation Manager. Instead of creating subclasses like XWindow and PMWindow for every window type, it uses a Window hierarchy bridged to a WindowImp implementation hierarchy containing XWindowImp and PMWindowImp.
You organize objects into part-whole tree hierarchies with the Composite pattern, allowing clients to treat individual leaf objects and composite container objects uniformly.
- Define a common Component abstract class representing both leaves and composites.
- Implement default behavior and child access operations in the Component class.
- Create Leaf subclasses for primitive elements and Composite subclasses that manage collections of child components.
The Decorator pattern attaches additional responsibilities to individual objects dynamically and transparently as a flexible alternative to static subclassing.
- Ensure the component and decorators descend from a lightweight common Component interface.
- Create a Decorator base class that maintains a reference to a Component and forwards all operations by default.
- Subclass Decorator to override specific operations and add extra responsibilities dynamically.
A document editor uses a TextView component and dynamically wraps it with a BorderDecorator to add a thick black border and a ScrollDecorator to add scroll bars.
You provide a unified, higher-level interface to a complex set of classes within a subsystem using the Facade pattern.
- Identify a complex subsystem with many interdependent internal classes.
- Create a Facade class that encapsulates common workflows and knows which subsystem objects are responsible for specific requests.
- Delegate client requests through the facade while keeping low-level classes accessible for advanced users.
The Flyweight pattern uses object sharing to support a massive number of fine-grained objects efficiently by separating intrinsic state from extrinsic state.
- Isolate context-independent intrinsic state to store inside the shared flyweight object.
- Externalize context-dependent state so that clients can compute or store it and pass it into flyweight operations.
- Implement a FlyweightFactory with an associative store to look up existing instances or create them if missing.
In a document editor using a character-based object structure, the Doc editor builds only one Glyph instance per character style. A document containing 180,000 characters required allocating only 480 character objects.
The Proxy pattern provides a surrogate or placeholder object that controls access to a real subject, enabling optimizations such as lazy initialization, remote representation, or access protection.
- Define a common Subject interface implemented by both the RealSubject and the Proxy.
- Configure the Proxy to maintain a reference or identifier to the real subject.
- Intercept client calls in the Proxy to handle tasks like lazy instantiation, access control, or remote communication before forwarding requests.
A document editor uses an ImageProxy object as a stand-in for a large raster image. The proxy stores the image file name and its bounding box extent to respond to layout formatters instantly, deferring the expensive creation and loading of the real Image object until its Draw method is explicitly invoked.
6. Behavioral Patterns
Behavioral patterns manage algorithms, communication, and assignment of responsibilities between objects to reduce tight coupling.
You can decouple the sender of a request from its receiver by giving multiple candidate objects a chance to handle the request along a chain. Requests are passed implicitly along a chain of receiver objects until one handles it or it falls off the end. Each object on the chain shares a common interface for handling requests and accessing its successor.
- Define a Handler interface with a request handling method and a successor link.
- Implement ConcreteHandlers that handle requests they are responsible for or forward them to their successor.
- Choose between creating new links or using existing object references like parent pointers in a hierarchy.
A context-sensitive help facility forwards a help request through a print dialog up to the application object when you click a print button.
You can encapsulate a request as an object, allowing you to parameterize clients with different requests, queue or log requests, and support undoable operations. The invoker is completely decoupled from the receiver that knows how to perform the operation.
- Define an abstract Command interface with an Execute operation.
- Create ConcreteCommand subclasses that bind a receiver object to specific actions.
- Implement an Unexecute operation and store commands in a history list to support undo and redo.
GUI menus and menu items use PasteCommand and OpenCommand objects to execute operations on underlying document and application receivers.
Given a language, you can define a representation for its grammar along with an interpreter that uses that representation to interpret sentences in the language. The pattern uses a class hierarchy to represent grammar rules, forming an abstract syntax tree.
- Define a grammar and represent each grammar rule with a class hierarchy.
- Build an abstract syntax tree using terminal and nonterminal expression classes.
- Define an Interpret operation on the expression classes to process input based on a context.
Classes like LiteralExpression, AlternationExpression, SequenceExpression, and RepetitionExpression represent regular expressions to match text patterns.
You can access the elements of an aggregate object sequentially without exposing its underlying representation by using an iterator object.
- Define an Iterator interface with operations like First, Next, IsDone, and CurrentItem.
- Implement ConcreteIterator subclasses to keep track of the current traversal position.
- Add a CreateIterator factory method to the aggregate interface for polymorphic iteration.
You can traverse a List or SkipList of Employee objects using a ListIterator or SkipListIterator without coupling yourself to the internal list structure.
You can define an object that encapsulates how a set of objects interact, promoting loose coupling by keeping objects from referring to each other explicitly. Communication is funneled through a central mediator object.
- Define a Mediator interface for communicating with colleague objects.
- Implement a ConcreteMediator that knows and coordinates its colleague objects.
- Ensure colleague objects communicate exclusively with their mediator instead of each other.
A FontDialogDirector coordinates interactions and dependencies between widgets like list boxes, entry fields, and buttons in a font dialog box.
You can capture and externalize an object's internal state without violating encapsulation, so that you can restore the object to this state later.
- Create a Memento class that stores the internal state of the Originator object.
- Provide a wide interface for the Originator and a narrow interface for the Caretaker.
- Have the Caretaker hold the memento and pass it back to the Originator to restore previous states.
A ConstraintSolver acts as an originator that creates a SolverState memento for an undo mechanism in a graphical editor maintaining connected shapes.
You can define a one-to-many dependency between objects so that when one object changes state, all its dependents are notified and updated automatically.
- Define abstract Subject and Observer interfaces.
- Implement Subject attachment and detachment methods to manage observers.
- Call Notify from the subject upon state changes, prompting observers to update their state.
A ClockTimer subject notifies DigitalClock and AnalogClock observer widgets every second when its tick operation runs.
You can allow an object to alter its behavior when its internal state changes, making the object appear to change its class.
- Define an abstract State class declaring an interface common to all operational states.
- Create ConcreteState subclasses to implement state-specific behavior and transitions.
- Maintain a state object reference in the Context and delegate state-specific requests to it.
A TCPConnection object delegates state-specific requests to TCPState subclasses such as TCPEstablished, TCPListen, and TCPClosed.
You can define a family of algorithms, encapsulate each one into a separate class, and make them interchangeable independently of the clients that use them.
- Define an abstract Strategy class declaring an interface common to all supported algorithms.
- Implement ConcreteStrategy classes to provide specific algorithm implementations.
- Configure the Context class with a ConcreteStrategy object and forward client requests to it.
A Composition class uses different Compositor subclasses like SimpleCompositor, TeXCompositor, and ArrayCompositor to handle text linebreaking strategies.
You can define the skeleton of an algorithm in an operation, deferring certain steps to subclasses without changing the algorithm's structure.
- Define the algorithm skeleton in a template method within an abstract class.
- Declare primitive operations or hook operations that subclasses must or can override.
- Ensure subclasses implement the varying steps while relying on the parent class for invariant structure.
An abstract Application class defines an OpenDocument template method that calls subclass-implemented primitive operations like CanOpenDocument, DoCreateDocument, and DoRead.
You can represent an operation to be performed on the elements of an object structure, letting you define a new operation without changing the element classes.
- Define an Accept operation in the Element class that takes a visitor as an argument.
- Create an abstract Visitor class with a Visit operation for each ConcreteElement class.
- Implement ConcreteVisitor subclasses to house specific algorithms and accumulated state.
PricingVisitor and InventoryVisitor subclasses operate on an Equipment node hierarchy to compute total cost and material inventory without altering equipment classes.
10 Key Ideas
- Design patterns systematically name, motivate, and explain recurring object-oriented design solutions to build flexible and maintainable software.
- Design patterns provide a common design vocabulary and consist of four essential elements: name, problem, solution, and consequences.
- You should program to an interface and favor object composition over class inheritance to ensure runtime flexibility.
- Building a document editor demonstrates how multiple design patterns interact to solve complex recursive composition and formatting problems.
- Creational design patterns abstract the object instantiation process, making systems independent of how objects are created, composed, and represented.
- Structural patterns combine classes and objects into larger structures while keeping them flexible, efficient, and decoupled.
- Behavioral patterns manage algorithms, communication, and assignment of responsibilities between objects to reduce tight coupling.
- The Abstract Factory pattern provides an interface for creating families of related or dependent objects without specifying their concrete classes.
- A drawing editor uses the Shape interface for graphical objects, but wants to reuse an off-the-shelf TextView class for text editing which has an incompatible interface.
- You can represent an operation to be performed on the elements of an object structure, letting you define a new operation without changing the element classes.