Introduction

27 min read

Overview

Designing object-oriented software is hard, and designing reusable object-oriented software is even harder. You must find pertinent objects, factor them into classes at the right granularity, define class interfaces and inheritance hierarchies, and establish key relationships among them. A reusable and flexible design is difficult if not impossible to get right the first time; experienced designers usually try to reuse a design several times, modifying it each time.

What experienced object-oriented designers know that novices do not is to avoid solving every problem from first principles. Instead they reuse solutions that have worked before, producing recurring patterns of classes and communicating objects across many systems. These patterns solve specific design problems and make designs more flexible, elegant, and ultimately reusable. The purpose of the book is to record this experience as design patterns: each pattern systematically names, explains, and evaluates an important and recurring design in object-oriented systems, captured in a catalog with a consistent format.

Design patterns make it easier to reuse successful designs and architectures, help you choose design alternatives that make a system reusable while avoiding those that compromise reusability, and improve the documentation and maintenance of existing systems by furnishing an explicit specification of class and object interactions and their underlying intent. None of the patterns describes new or unproven designs — only designs applied more than once in different systems. The catalog deliberately excludes patterns for concurrency, distributed or real-time programming, application-domain specifics, user interfaces, device drivers, and object-oriented databases.

Key takeaways

Mental model

The catalog is organized as a two-dimensional space — purpose (what a pattern does) crossed with scope (whether it applies to classes or objects). The mindmap below shows the three purpose families and the patterns in each, which is the primary index for navigating the rest of the catalog.

Mental model

What is a design pattern?

Christopher Alexander says, "Each pattern describes a problem which occurs over and over again in our environment, and then describes the core of the solution to that problem, in such a way that you can use this solution a million times over, without ever doing it the same way twice." Although Alexander wrote about buildings and towns, the same holds for object-oriented design patterns: our solutions are expressed in objects and interfaces instead of walls and doors, but at the core of both is a solution to a problem in a context.

The four essential elements

In general, a pattern has four essential elements:

  1. The pattern name is a handle used to describe a design problem, its solutions, and consequences in a word or two. Naming a pattern immediately increases the design vocabulary and lets you design at a higher level of abstraction. Finding good names was one of the hardest parts of developing the catalog.
  2. The problem describes when to apply the pattern — it explains the problem and its context. It might describe specific design problems (such as how to represent algorithms as objects), or class/object structures symptomatic of an inflexible design. Sometimes it includes a list of conditions that must be met before applying the pattern makes sense.
  3. The solution describes the elements that make up the design, their relationships, responsibilities, and collaborations. It does not describe a particular concrete design or implementation; a pattern is like a template that can be applied in many different situations. It provides an abstract description of a design problem and how a general arrangement of classes and objects solves it.
  4. The consequences are the results and trade-offs of applying the pattern. Though often unvoiced, consequences are critical for evaluating design alternatives and understanding the costs and benefits of applying a pattern. For software they often concern space and time trade-offs, language and implementation issues, and — because reuse is a frequent factor — a pattern's impact on a system's flexibility, extensibility, or portability.

The level of abstraction

Point of view affects what is and isn't a pattern: one person's pattern can be another's primitive building block. The patterns in this book are pitched at a particular level. They are not designs such as linked lists and hash tables that can be encoded in classes and reused as-is, nor are they complex domain-specific designs for an entire application or subsystem. A design pattern names, abstracts, and identifies the key aspects of a common design structure that make it useful for creating a reusable object-oriented design — the participating classes and instances, their roles and collaborations, and the distribution of responsibilities.

The patterns are based on practical solutions implemented in mainstream object-oriented languages — Smalltalk and C++ — rather than procedural languages (Pascal, C, Ada) or more dynamic object-oriented languages (CLOS, Dylan, Self). The choice of language matters because it influences point of view: assuming procedural languages might have produced patterns called "Inheritance," "Encapsulation," and "Polymorphism," and some languages support certain patterns directly (CLOS multi-methods lessen the need for Visitor).

Design patterns in Smalltalk MVC

The Model/View/Controller (MVC) triad of classes is used to build user interfaces in Smalltalk-80, and its internal design patterns illustrate what "pattern" means. MVC consists of three kinds of objects: the Model is the application object, the View is its screen presentation, and the Controller defines the way the user interface reacts to user input. Before MVC, interface designs lumped these together; MVC decouples them to increase flexibility and reuse.

  • Observer. MVC decouples views and models with a subscribe/notify protocol: when the model's data changes it notifies dependent views, and each view updates itself. This lets you attach multiple views to a model and create new views without rewriting the model. The general problem — decoupling objects so a change in one can affect any number of others without the changed object knowing the others' details — is described by the Observer pattern.
  • Composite. Views can be nested: a control panel of buttons is a complex view containing nested button views, supported by the CompositeView subclass of View. A composite view can be used wherever a view can. The general problem — grouping objects and treating the group like an individual object — is described by the Composite pattern.
  • Strategy. MVC lets you change how a view responds to input without changing its visual presentation by encapsulating the response mechanism in a Controller object; you can even swap a view's controller at run-time (a controller that ignores events disables the view). The View–Controller relationship is an example of the Strategy pattern: a Strategy is an object that represents an algorithm, useful when you want to replace the algorithm statically or dynamically, have many variants, or want to encapsulate complex algorithm data.

MVC also uses Factory Method to specify the default controller class for a view and Decorator to add scrolling, but its main relationships are given by Observer, Composite, and Strategy.

Describing design patterns

Graphical notations alone are not sufficient to describe a pattern — they capture only the end product as relationships between classes and objects. To reuse the design you must also record the decisions, alternatives, and trade-offs that led to it, plus concrete examples. The GoF describe every pattern with a consistent template, which lends a uniform structure that makes patterns easier to learn, compare, and use:

| Template section | What it answers | | --- | --- | | Pattern Name and Classification | The pattern's name (which becomes part of your design vocabulary) and its classification under the purpose/scope scheme. | | Intent | What the pattern does, its rationale, and the particular design issue or problem it addresses. | | Also Known As | Other well-known names for the pattern, if any. | | Motivation | A scenario illustrating a design problem and how the pattern's class/object structures solve it. | | Applicability | The situations in which the pattern can be applied; examples of poor designs it can address; how to recognize those situations. | | Structure | A graphical representation of the classes using OMT-based notation, plus interaction diagrams for sequences of requests and collaborations. | | Participants | The classes and/or objects participating in the pattern and their responsibilities. | | Collaborations | How the participants collaborate to carry out their responsibilities. | | Consequences | How the pattern supports its objectives, the trade-offs and results of using it, and what aspect of system structure it lets you vary independently. | | Implementation | Pitfalls, hints, techniques, and language-specific issues to be aware of when implementing the pattern. | | Sample Code | Code fragments illustrating an implementation in C++ or Smalltalk. | | Known Uses | Examples of the pattern found in real systems (at least two from different domains). | | Related Patterns | Closely related patterns, important differences, and which patterns to use together. |

Three appendices support the patterns: Appendix A is a glossary, Appendix B presents the OMT-based notations, and Appendix C contains source code for the foundation classes used in code samples.

The catalog of design patterns

The catalog contains 23 design patterns. Their names and intents follow (the number in parentheses is the page number in the book, a convention the book follows throughout).

| Pattern | Intent | | --- | --- | | Abstract Factory (87) | Provide an interface for creating families of related or dependent objects without specifying their concrete classes. | | Adapter (139) | Convert the interface of a class into another interface clients expect. Adapter lets classes work together that couldn't otherwise because of incompatible interfaces. | | Bridge (151) | Decouple an abstraction from its implementation so that the two can vary independently. | | Builder (97) | Separate the construction of a complex object from its representation so that the same construction process can create different representations. | | Chain of Responsibility (223) | Avoid coupling the sender of a request to its receiver by giving more than one object a chance to handle the request. Chain the receiving objects and pass the request along the chain until an object handles it. | | Command (233) | Encapsulate a request as an object, thereby letting you parameterize clients with different requests, queue or log requests, and support undoable operations. | | Composite (163) | Compose objects into tree structures to represent part-whole hierarchies. Composite lets clients treat individual objects and compositions of objects uniformly. | | Decorator (175) | Attach additional responsibilities to an object dynamically. Decorators provide a flexible alternative to subclassing for extending functionality. | | Facade (185) | Provide a unified interface to a set of interfaces in a subsystem. Facade defines a higher-level interface that makes the subsystem easier to use. | | Factory Method (107) | Define an interface for creating an object, but let subclasses decide which class to instantiate. Factory Method lets a class defer instantiation to subclasses. | | Flyweight (195) | Use sharing to support large numbers of fine-grained objects efficiently. | | Interpreter (243) | Given a language, define a representation for its grammar along with an interpreter that uses the representation to interpret sentences in the language. | | Iterator (257) | Provide a way to access the elements of an aggregate object sequentially without exposing its underlying representation. | | Mediator (273) | Define an object that encapsulates how a set of objects interact. Mediator promotes loose coupling by keeping objects from referring to each other explicitly, and it lets you vary their interaction independently. | | Memento (283) | Without violating encapsulation, capture and externalize an object's internal state so that the object can be restored to this state later. | | Observer (293) | Define a one-to-many dependency between objects so that when one object changes state, all its dependents are notified and updated automatically. | | Prototype (117) | Specify the kinds of objects to create using a prototypical instance, and create new objects by copying this prototype. | | Proxy (207) | Provide a surrogate or placeholder for another object to control access to it. | | Singleton (127) | Ensure a class only has one instance, and provide a global point of access to it. | | State (305) | Allow an object to alter its behavior when its internal state changes. The object will appear to change its class. | | Strategy (315) | Define a family of algorithms, encapsulate each one, and make them interchangeable. Strategy lets the algorithm vary independently from clients that use it. | | Template Method (325) | Define the skeleton of an algorithm in an operation, deferring some steps to subclasses. Template Method lets subclasses redefine certain steps of an algorithm without changing the algorithm's structure. | | Visitor (331) | Represent an operation to be performed on the elements of an object structure. Visitor lets you define a new operation without changing the classes of the elements on which it operates. |

Organizing the catalog

Design patterns vary in granularity and level of abstraction, so a classification is needed both to refer to families of related patterns and to direct the search for new ones. The patterns are classified by two criteria.

Purpose

The first criterion, purpose, reflects what a pattern does:

  • Creational patterns concern the process of object creation.
  • Structural patterns deal with the composition of classes or objects.
  • Behavioral patterns characterize the ways in which classes or objects interact and distribute responsibility.

Scope

The second criterion, scope, specifies whether the pattern applies primarily to classes or to objects:

  • Class patterns deal with relationships between classes and their subclasses. These relationships are established through inheritance, so they are static — fixed at compile-time. Almost all patterns use inheritance to some extent, so the only patterns labeled "class patterns" are those that focus on class relationships.
  • Object patterns deal with object relationships, which can be changed at run-time and are more dynamic. Most patterns are in the object scope.

The two criteria cross to form the design pattern space (Table 1.1):

| | Creational | Structural | Behavioral | | --- | --- | --- | --- | | Class scope | Factory Method | Adapter (class) | Interpreter, Template Method | | Object scope | Abstract Factory, Builder, Prototype, Singleton | Adapter (object), Bridge, Composite, Decorator, Facade, Flyweight, Proxy | Chain of Responsibility, Command, Iterator, Mediator, Memento, Observer, State, Strategy, Visitor |

How purpose and scope combine refines what each cell means: creational class patterns defer some part of object creation to subclasses, while creational object patterns defer it to another object; structural class patterns use inheritance to compose classes, while structural object patterns describe ways to assemble objects; behavioral class patterns use inheritance to describe algorithms and flow of control, whereas behavioral object patterns describe how a group of objects cooperate to perform a task no single object can carry out alone.

There are other ways to organize patterns: some are often used together (Composite with Iterator or Visitor); some are alternatives (Prototype is often an alternative to Abstract Factory); some result in similar designs despite different intents (the structure diagrams of Composite and Decorator are similar). Yet another organization follows how patterns reference each other in their "Related Patterns" sections (Figure 1.1). Having multiple ways of thinking about patterns deepens insight into what they do, how they compare, and when to apply them.

How design patterns solve design problems

Design patterns solve many day-to-day problems object-oriented designers face. This section walks through those problems.

Finding appropriate objects

An object packages both data and the procedures (called methods or operations) that operate on that data. An object performs an operation when it receives a request (or message) from a client. Requests are the only way to get an object to execute an operation, and operations are the only way to change an object's internal data; because of these restrictions the object's internal state is encapsulated — it cannot be accessed directly and its representation is invisible from outside.

The hard part of object-oriented design is decomposing a system into objects, because many conflicting factors come into play: encapsulation, granularity, dependency, flexibility, performance, evolution, reusability. Object-oriented designs often end up with classes that have no real-world counterpart — abstractions that emerge during design are key to making a design flexible. Design patterns help you identify these less-obvious abstractions: Strategy describes interchangeable families of algorithms; State represents each state of an entity as an object. Such objects are seldom found during analysis and are discovered later when making a design more flexible and reusable.

Determining object granularity

Objects can vary tremendously in size and number, from hardware up to entire applications. Patterns help decide what should be an object: Facade describes how to represent complete subsystems as objects; Flyweight supports huge numbers of objects at the finest granularities; Abstract Factory and Builder yield objects whose only responsibility is creating other objects; Visitor and Command yield objects whose only responsibility is to implement a request on another object or group of objects.

Specifying object interfaces

Every operation declared by an object specifies the operation's name, the objects it takes as parameters, and its return value — the operation's signature. The set of all signatures defined by an object's operations is the interface to the object, which characterizes the complete set of requests that can be sent to it. A type is a name used to denote a particular interface; an object can have many types, and widely different objects can share a type. A type is a subtype of another if its interface contains the interface of its supertype, and we say the subtype inherits the interface of its supertype.

Interfaces are fundamental: objects are known only through their interfaces, and an interface says nothing about implementation, so two objects with completely different implementations can have identical interfaces. When a request is sent to an object, the particular operation performed depends on both the request and the receiving object; this run-time association of a request to an object and one of its operations is dynamic binding. Dynamic binding lets you substitute objects with identical interfaces for one another at run-time — polymorphism — which simplifies the definitions of clients, decouples objects from each other, and lets them vary their relationships at run-time.

Patterns help define interfaces: Memento stipulates two interfaces (a restricted one for clients that hold and copy mementos, and a privileged one only the originator uses to store and retrieve state). Patterns also specify relationships between interfaces — Decorator and Proxy require their interfaces to be identical to the decorated/proxied objects, and a Visitor interface must reflect all classes it can visit.

Specifying object implementations

An object's implementation is defined by its class, which specifies the object's internal data and representation and defines the operations it can perform. The book's OMT-based notation depicts a class as a rectangle with the class name in bold, operations in normal type below it, and data after the operations. Objects are created by instantiating a class; the object is then an instance of the class.

New classes can be defined in terms of existing classes using class inheritance: a subclass includes the definitions of all data and operations the parent defines, and may override an operation to handle requests instead of its parent. An abstract class defines a common interface for its subclasses, defers some or all implementation to operations defined in subclasses (its abstract operations), and cannot be instantiated; classes that aren't abstract are concrete classes. A mixin class provides an optional interface or functionality to other classes; like an abstract class it isn't meant to be instantiated, and it requires multiple inheritance.

Class versus interface inheritance

It is important to distinguish an object's class (how the object is implemented — its internal state and the implementation of its operations) from its type (only its interface — the set of requests to which it can respond). An object can have many types, and objects of different classes can have the same type. Class defines type because the operations a class defines also define the object's type.

Equally important is the difference between class inheritance and interface inheritance (subtyping): class inheritance defines an object's implementation in terms of another object's implementation (a mechanism for code and representation sharing), whereas interface inheritance describes when an object can be used in place of another. Many languages don't make the distinction explicit — in C++ and Eiffel inheritance means both interface and implementation inheritance; in Smalltalk inheritance means just implementation inheritance. Many patterns depend on this distinction: objects in a Chain of Responsibility need a common type but usually not a common implementation; Command, Observer, State, and Strategy are often implemented with abstract classes that are pure interfaces.

Programming to an interface, not an implementation

Class inheritance lets you define a new object rapidly in terms of an old one and get new implementations almost for free. But implementation reuse is only half the story — inheritance's ability to define families of objects with identical interfaces (usually by inheriting from an abstract class) is also important, because polymorphism depends on it. When inheritance is used carefully, all classes derived from an abstract class share its interface, so a subclass merely adds or overrides operations and does not hide operations of the parent.

There are two benefits to manipulating objects solely through the interface defined by abstract classes:

  1. Clients remain unaware of the specific types of objects they use, as long as the objects adhere to the interface the clients expect.
  2. Clients remain unaware of the classes that implement these objects; they only know about the abstract class(es) defining the interface.

This reduces implementation dependencies between subsystems so greatly that it leads to the first principle of reusable object-oriented design:

Author's principle: Program to an interface, not an implementation.

Don't declare variables to be instances of particular concrete classes; commit only to an interface defined by an abstract class. Concrete classes must be instantiated somewhere, and the creational patterns (Abstract Factory, Builder, Factory Method, Prototype, Singleton) let you do that — by abstracting object creation, they associate an interface with its implementation transparently at instantiation, ensuring your system is written in terms of interfaces, not implementations.

Putting reuse mechanisms to work

Most people understand objects, interfaces, classes, and inheritance; the challenge is applying them to build flexible, reusable software. This section compares the reuse mechanisms.

Inheritance versus composition

The two most common reuse techniques are class inheritance and object composition.

  • Class inheritance (reuse by subclassing) is called white-box reuse — "white-box" referring to visibility, because the internals of parent classes are often visible to subclasses. It is defined statically at compile-time, is straightforward, and makes it easy to modify the implementation being reused. Its disadvantages: you can't change inherited implementations at run-time; parent classes often define part of their subclasses' physical representation, so "inheritance breaks encapsulation" — a subclass becomes so bound to its parent's implementation that any change to the parent forces the subclass to change. One cure is to inherit only from abstract classes, which usually provide little or no implementation.
  • Object composition (assembling objects to get more complex functionality) is called black-box reuse, because no internal details of objects are visible — objects appear only as "black boxes." It is defined dynamically at run-time through objects acquiring references to other objects, requires objects to respect each other's interfaces, but does not break encapsulation. Any object can be replaced at run-time by another of the same type, and there are substantially fewer implementation dependencies. Favoring composition keeps each class encapsulated and focused on one task, so class hierarchies stay small — at the cost of more objects and behavior that depends on object interrelationships rather than being defined in one class.

This leads to the second principle:

Author's principle: Favor object composition over class inheritance.

Ideally you'd achieve all reuse by composing existing components, but the available set is never rich enough in practice; reuse by inheritance makes it easier to make new components that can be composed with old ones, so the two work together. The authors' experience is that designers overuse inheritance, and designs are often made more reusable and simpler by depending more on composition.

Delegation

Delegation makes composition as powerful for reuse as inheritance. In delegation two objects handle a request: a receiving object delegates operations to its delegate. This is analogous to subclasses deferring requests to parent classes, but to let the delegated operation refer to the receiver, the receiver passes itself to the delegate. For example, instead of making Window a subclass of Rectangle, Window keeps a Rectangle instance variable and delegates rectangle-specific behavior to it — Window has a Rectangle rather than is a Rectangle, and must forward requests explicitly.

The main advantage is that you can compose behaviors at run-time and change how they're composed (a window can become circular by swapping its Rectangle for a Circle of the same type). The disadvantage it shares with other composition techniques: dynamic, highly parameterized software is harder to understand, and there are run-time inefficiencies. Delegation is a good choice only when it simplifies more than it complicates, and works best in highly stylized ways — that is, in standard patterns. State, Strategy, and Visitor depend on delegation; Mediator, Chain of Responsibility, and Bridge use it more lightly. Delegation is an extreme example of object composition, showing you can always replace inheritance with composition as a mechanism for code reuse.

Inheritance versus parameterized types

Parameterized types — also known as generics (Ada, Eiffel) and templates (C++) — let you define a type without specifying all the other types it uses; the unspecified types are supplied as parameters at the point of use (a List class can be parameterized by the type of elements it contains). This gives a third way to compose behavior. To parameterize a sorting routine by the comparison it uses, you could make the comparison:

  1. an operation implemented by subclasses (an application of Template Method),
  2. the responsibility of an object passed to the sorting routine (Strategy), or
  3. an argument of a C++ template or Ada generic specifying the function to call.

The techniques differ: object composition lets you change behavior at run-time but requires indirection and can be less efficient; inheritance provides default implementations subclasses can override; parameterized types let you change the types a class uses — but neither inheritance nor parameterized types can change at run-time. None of the book's patterns concerns parameterized types, though they are occasionally used to customize a C++ implementation (and aren't needed at all in Smalltalk, which has no compile-time type checking).

Relating run-time and compile-time structures

An object-oriented program's run-time structure often bears little resemblance to its code structure. The code structure is frozen at compile-time as classes in fixed inheritance relationships; the run-time structure consists of rapidly changing networks of communicating objects, and the two are largely independent. Consider two relationships:

  • Aggregation implies one object owns or is responsible for another — the aggregate and its owner have identical lifetimes.
  • Acquaintance (also "association" or the "using" relationship) implies an object merely knows of another; acquainted objects may request operations of each other but aren't responsible for each other. It is weaker than aggregation and suggests much looser coupling.

The two are easy to confuse because they are often implemented the same way (in Smalltalk all variables are references; in C++ both can use pointers or references). Ultimately they are determined more by intent than by language mechanisms: aggregation relationships tend to be fewer and more permanent, while acquaintances are made and remade more frequently and are harder to discern in source code. Because run-time and compile-time structures diverge so much, the system's run-time structure must be imposed more by the designer than the language. Many object-scope patterns capture this distinction explicitly — Composite and Decorator build complex run-time structures, and Observer and Chain of Responsibility produce run-time communication that inheritance does not reveal.

Designing for change

The key to maximizing reuse is anticipating new and changed requirements and designing systems that can evolve accordingly. A design that doesn't account for change risks major redesign — class redefinition and reimplementation, client modification, and retesting — and unanticipated changes are invariably expensive. Each design pattern lets some aspect of system structure vary independently of other aspects, making a system more robust to a particular kind of change. Here are common causes of redesign and the patterns that address them.

| Cause of redesign | Patterns that address it | | --- | --- | | 1. Creating an object by specifying a class explicitly — commits you to an implementation instead of an interface. Create objects indirectly. | Abstract Factory, Factory Method, Prototype | | 2. Dependence on specific operations — commits you to one way of satisfying a request. Avoid hard-coded requests. | Chain of Responsibility, Command | | 3. Dependence on hardware and software platform — external OS and API differences make software harder to port. Limit platform dependencies. | Abstract Factory, Bridge | | 4. Dependence on object representations or implementations — clients that know how an object is represented, stored, located, or implemented must change when it changes. Hide this information. | Abstract Factory, Bridge, Memento, Proxy | | 5. Algorithmic dependencies — algorithms are extended, optimized, and replaced; dependent objects must change too. Isolate algorithms likely to change. | Builder, Iterator, Strategy, Template Method, Visitor | | 6. Tight coupling — tightly coupled classes are hard to reuse in isolation and lead to monolithic systems. Use abstract coupling and layering for loose coupling. | Abstract Factory, Bridge, Chain of Responsibility, Command, Facade, Mediator, Observer | | 7. Extending functionality by subclassing — subclassing has fixed overhead, requires deep understanding of the parent, and can explode the class count. Use composition and delegation as flexible alternatives. | Bridge, Chain of Responsibility, Composite, Decorator, Observer, Strategy | | 8. Inability to alter classes conveniently — you may lack the source, or any change would require modifying many subclasses. | Adapter, Decorator, Visitor |

How crucial such flexibility is depends on the kind of software being built. The book frames three broad classes:

Application programs

For an application program (a document editor, a spreadsheet), internal reuse, maintainability, and extension are high priorities. Patterns that reduce dependencies increase internal reuse — looser coupling boosts the likelihood that one class can cooperate with several others, and eliminating dependencies on specific operations, algorithms, and representations makes operations easier to reuse in different contexts. Patterns also make applications more maintainable by limiting platform dependencies and layering the system, and more extensible by showing how to extend class hierarchies and exploit object composition.

Toolkits

A toolkit is a set of related, reusable classes designed to provide useful, general-purpose functionality (a set of collection classes; the C++ I/O stream library). Toolkits don't impose a design on your application — they just provide functionality that helps it do its job, and they emphasize code reuse, being the object-oriented equivalent of subroutine libraries. Toolkit design is arguably harder than application design because a toolkit must work in many applications, and its writer can't know those applications or their special needs — making it crucial to avoid assumptions and dependencies that limit flexibility.

Frameworks

A framework is a set of cooperating classes that make up a reusable design for a specific class of software (a framework for graphical editors, compilers, or financial modeling). You customize a framework by creating application-specific subclasses of its abstract classes. The framework dictates the architecture of your application — its overall structure, partitioning into classes and objects, key responsibilities, how classes collaborate, and the thread of control — so frameworks emphasize design reuse over code reuse.

This leads to an inversion of control: with a toolkit you write the main body of the application and call the code you want to reuse; with a framework you reuse the main body and write the code it calls. You build applications faster and they share similar structures (easier to maintain, more consistent to users), at the cost of some creative freedom. Frameworks are the hardest software of all to design — a framework designer gambles that one architecture will work for all applications in the domain, and applications are particularly sensitive to changes in framework interfaces, making loose coupling all the more important. Mature frameworks usually incorporate several design patterns, which make the architecture suitable to many applications without redesign and, when documented, help people gain insight into the framework faster.

Patterns and frameworks differ in three major ways:

  1. Patterns are more abstract than frameworks. Frameworks can be embodied in code; only examples of patterns can be. Design patterns also explain the intent, trade-offs, and consequences of a design.
  2. Patterns are smaller architectural elements than frameworks. A typical framework contains several design patterns, but the reverse is never true.
  3. Patterns are less specialized than frameworks. Frameworks always have a particular application domain; the patterns in this catalog can be used in nearly any kind of application.

How to select a design pattern

With more than 20 patterns to choose from, finding the one that addresses a particular problem can be hard. Several approaches help:

  • Consider how design patterns solve design problems. Refer to the discussions of finding appropriate objects, determining granularity, specifying interfaces, and so on, to guide your search.
  • Scan Intent sections. Read through each pattern's intent to find those relevant to your problem, using the purpose/scope classification to narrow the search.
  • Study how patterns interrelate. The graph of relationships between patterns can direct you to the right pattern or group of patterns.
  • Study patterns of like purpose. The catalog has three chapters — creational, structural, behavioral — each opening with introductory comments and closing with a section comparing and contrasting its patterns.
  • Examine a cause of redesign. Check whether your problem involves one of the causes of redesign, then look at the patterns that help you avoid it.
  • Consider what should be variable in your design. The opposite of focusing on causes of redesign: instead of what might force a change, consider what you want to be able to change without redesign. The focus is on encapsulating the concept that varies — a theme of many patterns — and Table 1.2 lists the design aspect each pattern lets you vary independently.

How to use a design pattern

Once you've picked a pattern, apply it effectively with this step-by-step approach:

  1. Read the pattern once through for an overview. Pay particular attention to the Applicability and Consequences sections to ensure the pattern is right for your problem.
  2. Study the Structure, Participants, and Collaborations sections. Make sure you understand the classes and objects in the pattern and how they relate.
  3. Look at the Sample Code section to see a concrete example in code; studying the code helps you learn how to implement the pattern.
  4. Choose meaningful names for pattern participants in the application context. The abstract participant names are usually too abstract to appear directly, but incorporating the participant name into the application name makes the pattern more explicit (for the Strategy pattern in a text compositing algorithm you might have SimpleLayoutStrategy or TeXLayoutStrategy).
  5. Define the classes. Declare their interfaces, establish inheritance relationships, and define the instance variables that represent data and object references. Identify and modify existing classes the pattern affects.
  6. Define application-specific names for operations. Use the responsibilities and collaborations associated with each operation as a guide, and be consistent (for example, a "Create-" prefix to denote a factory method).
  7. Implement the operations to carry out the responsibilities and collaborations in the pattern, guided by the Implementation and Sample Code sections.

Continue exploring

Tags