Programming in the Large with Design Patterns (2012)
Chapter 1. Introduction to Design Patterns
Imagine you are in the late stages of designing a library automation system. The requirements call for a system that will allow a library patron to:
1. Check out a book
2. Return a book
3. Renew a book, and
4. Place a book on reserve
Figure 1 Use case diagram for library automation system
After a few iterations of original design you decide on the following 3 classes for the business logic:
Figure 2 Class diagram for library automation system
It’s a workable design but not a great design. It addresses all the requirements, but the main features (shown in bold in Figure 2) are distributed over three classes. The user interface (UI) subsystem that will make use of the features will have dependencies on all three classes. The coupling between the UI subsystem and core logic could be reduced if all features were accessible from a single component.
How would you go about solving this design problem?
If you fancy yourself an engineer, your first inclination should be to look for a routine or existing design solution before resorting to original design. For software engineers this means consulting handbooks on design patterns. Luckily you are holding one in your hand right now. Read through the design pattern digest at the front of the book to see if any of the patterns might offer a solution to the current design problem.
Reading through the design pattern summaries at the front of this book, there is one that stands out:
Façade—The intent of the Façade design pattern is to “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” (Gamma, et al. 1995).
Façade seems to be a good match for the problem at hand. The full write-up for the pattern in chapter 10 provides guidelines for applying the pattern in different contexts. The result after applying it to the current design problem would look something like:
Figure 3 Unified interface onto library automation subsystem
In this example, high coupling signaled a weakness in the design, which was then remedied with the Façade design pattern. Design will always involve some level of creativity and innovation but skillful application of design principles and patterns can help make the process a little more routine.
History of Design Patterns
Design patterns are reusable solutions to reoccurring design problems. The idea of designing with patterns didn’t start with software though; it started in the realm of urban planning and building architecture. In the 1970's, Christopher Alexander set out to create a pattern language that ordinary people could use to create more enjoyable buildings and public spaces. The first book on patterns, A Pattern Language: Towns, Buildings, Construction, documented 253 such patterns. Each pattern provided a general solution to a reoccurring design problem in a particular context.
For another perspective on how patterns work in general, consider Low Sill, a pattern proposed by Christopher Alexander for determining the height of a windowsill. Here is a summary of the pattern in standard form (Alexander, et al., 1977):
Context: Windows are being planned for a wall.
Problem: How high should the windowsill be from the floor? A windowsill that is too high cuts you off from the outside world. One that is too low could be mistaken for a door and is potentially unsafe.
Solution: Design the windowsill to be 12 to 14 inches from the floor. On upper floors, for safety, make them higher, around 20 inches. The primary function of a window is to connect building occupants to the outside world. The link is more meaningful when both the ground and horizon are visible when standing a foot or two away from the window.
Figure 4 Low windowsill (Photo credit: Searl Lamaster Howe Architects)
This example illustrates a couple of important characteristics of patterns. First, design (and more generally engineering) is about balancing conflicting forces or constraints. Here the objective is to make the windowsill low enough to see the ground in order to make a connection with the outside world but high enough to be safe and not to be confused with a door.
Second, design patterns provide general solutions at a medium level of abstraction. They don't give exact answers (precise measurements in the case of Low Sill), and at the same time, they are more concrete and practical than abstract principles or strategies. In the example above, the solution is given in terms of a range of heights: 12 to 20 inches. How high to set a window will depend on preferences and local conditions. The optimal height is the value that best balances the conflicting forces.
Finally, patterns aren't dogma. The pattern doesn't say that all windowsills must be 12 to 20 inches off the floor. If you are designing a jail cell, for example, there may be overriding factors that call for a windowsill higher than the recommended 12 to 20 inches.
Ten years after the publication of Christopher Alexander’s book on architectural patterns, Kent Beck and Ward Cunningham proposed creating a pattern language for software design analogous to Christopher Alexander’s pattern language for town and building design (Beck and Cunningham 1987). In support of their suggestion, they gave a brief experience report on 5 patterns they created for user interface design.
Just as Christopher Alexander envisioned ordinary citizens using architectural patterns to design and build the personal spaces they would occupy, Kent Beck and Ward Cunningham envisioned software design patterns empowering computer users to “write their own programs” (Smith 1987).
Our initial success using a pattern language for user interface design has left us quite enthusiastic about the possibilities for computer users designing and programming their own applications (Smith 1987).
Neither collection of patterns has made significant inroads with end users, but both have been quite successful at documenting design knowledge in a way that makes it accessible and reusable—if only for professionals in the domain.
Figure 5 Important milestones in the history of design patterns
The software patterns movement began in earnest after the publication of Design Patterns: Elements of Reusable Object-Oriented Software (Gamma, et al. 1995). The four authors Erich Gamma, Richard Helm, Ralph Johnson and John Vlissides are collectively known as the “Gang of Four” or GofF. Many of the patterns in this book are widely accepted in the development community and therefore required knowledge for software development professionals.
What is a Software Design Pattern?
A software design pattern is a reusable solution to a reoccurring software design problem. (For the rest of this chapter, assume the term design pattern refers to a software design pattern.) A design pattern is not a concrete design or implementation, such as an algorithm that can be used as-is, but rather a generic solution that must be adapted to the specific needs of the problem at hand.
To better understand what a design pattern is, recall the process and product of design. The purpose of the design process is to determine how the eventual code will be structured or organized into modules. The output of the design process is an abstract solution model typically expressed with a symbolic modeling language such as UML.
Figure 6 The output of the design process is an abstract solution model
Design is probably the most challenging activity in the software development life cycle. There is no algorithm for deriving abstract solution models from requirements. The best the discipline of software engineering can offer are methods, techniques, heuristics, and the subject of this book, design patterns.
A design pattern is problem-solution pair. A design pattern provides a mapping from a specific design problem to a generic solution.
Figure 7 A design pattern is a problem-solution pair
Knowledge of design patterns simplifies software design by reducing the number of design problems that have to be solved from first principles. Design problems that match documented design patterns have ready-made solutions. The remaining problems that don't match documented design patterns must be solved from first principles. Even here, knowledge of design patterns can potentially help with original design. Design patterns are paragons of good design. Studying design patterns helps to develop the intellectual concepts and principles needed to solve unique design problems from first principles.
Figure 8 Knowledge of design patterns reduces the number of design problems that have to be solved from first principles
Reverse Engineering a Design Pattern
A design pattern, and more generally a design, is an abstraction of an implementation. Because the human mind is better at forming abstractions from details, maybe the best way to get a deeper understanding of design patterns is to reverse engineer one from implementation. In this example I will extract the design from two code samples and then present the design pattern that corresponds to the design.
The following will be familiar to anyone who has written a GUI program in Java:
Figure 9 Event handling in Java
Likewise, the following will be familiar to anyone who has written a GUI program in C#:
Figure 10 Event handling in C#
In each case an event handler or callback routine is registered to handle button click events. When the user clicks on the visible button, control passes to the registered event handler. Each implementation is unique, but in both cases the design is the same. In both cases the general design problem is how to allow one or more objects (those containing the event handling routines) to be notified when the state of another object (the button) changes. This is a routine design problem for which there exists a reusable solution in the form of the Observer design pattern.
Figure 11 Two implementations for the same abstract design
The observer design pattern will be discussed in greater detail in chapter 9, but to give you some idea what a design pattern looks like, here is the Observer design pattern in outline form:
Pattern Name: Observer.
Context: One or more objects (observers) need to be made aware of state changes in another object (the subject).
Problem: The objects doing the observing (observers) should be decoupled from the object under observation (the subject). It should be possible to evolve and reuse subjects or observers independently of each other.
The subject shouldn’t depend on the details of its observers. (The design complies with the dependency inversion principle.) Adding another observer shouldn’t require a change to the subject.
Solution: Define a class or interface Subject with methods for attaching and detaching observers as well as a method for notifying attached observers when the state of the subject changes. Define an interface Observer that defines a callback method subjects can use to notify observers of a change.
Because natural language is not well suited to conveying design ideas (admit it, your eyes glazed over while reading the preceding paragraph), design patterns are often expressed using a visual modeling language such as UML. The following UML diagram shows the abstract solution model for the Observer design pattern.
Figure 12 Abstract solution model for Observer design pattern
In this example, for pedagogical reasons, the code came before the design. In practice, you would of course discover the opportunity to use the Observer design pattern during design and then use the design pattern to derive the code.
It’s hard to keep a good idea from spreading. The use of patterns has branched out from design to find application in other stages of the software life cycle. Software patterns can be classified according to the development stage during which they are used: analysis, design and implementation. Patterns of design can further be distinguished by level of abstraction: high-level and mid-level. As Figure 13 shows, the four main categories of software patterns are: analysis patterns, architectural patterns, design patterns and programming idioms.
Figure 13 Patterns throughout the software life cycle
Analysis Patterns. Understanding the problem domain is an important part of the requirements process. This involves looking beyond the surface requirements in order to gain a deeper understanding of the underlying concepts in the problem domain. To illustrate, here is an example of concept discovery during the course of requirements elicitation:
User: "We need to send out invoices."
Requirements Analyst: "I assume you send these invoices to other companies."
User: "Yes, that is correct…um, most of the time. Actually, some of the time we send them out to private individuals."
Requirements Analyst: "So, the recipient of an invoice could be a company or an individual."
User: "Yes, and government agencies too. They are like companies but are handled slightly differently."
What is slowly emerging here is the concept of responsible party. It is one of many concepts that experienced analysts know to look for when doing domain analysis or business modeling.
Just as design patterns document collective knowledge of common designs, analysis patterns document collective knowledge of common abstractions found in business modeling.
Figure 14 shows what the Responsible Party analysis pattern might look like in a domain model.
Figure 14 UML diagram for the Responsible Party analysis pattern
The UML analysis model in Figure 14 could easily be mistaken for a design. It is an analysis model by virtue of how it is used. The motivation for using analysis patterns is the desire to better understand the problem domain. However, because analysis models make excellent first-draft design models, it is not unusual for analysis models to evolve into design models, sometime with little or no modification.
The best reference for analysis patterns is Martin Fowler's book, Analysis Patterns (Fowler 1997). It documents 76 analysis patterns including Party and other familiar domain concepts such as Account, Contract, Product, Measurement and Quantity.
Architectural Patterns. Design occurs at different levels. The scale of the components of interest distinguishes the levels. At the architectural level, components are programs and subsystems. For mid-level design, components are classes and objects.
An architectural pattern is a high-level plan for organizing the top-level components of a program or software system.
Figure 15 Scope of architecture and design patterns
Well-known architectural patterns (or styles) include: client-server, n-tiered, blackboard, and model-view controller (Buschmann, et al. 1996) (Shaw and Garlan 1996).
Design Patterns. As Figure 15 illustrates, design patterns address mid-level design problems. The solutions to these problems are defined by a small number of classes and/or objects. Design patterns are the subject of this book.
Programming Idioms. A programming idiom is a low-level, language-specific pattern that explains how to accomplish a simple task using a specific programming language or technology. For example, the following C++ idiom explains how to prevent objects in a C++ program from being copied (Coplien 1992).
Context: In C++, objects are copied when passed by value or assigned. The design of a class may be such that copying an instance of the class would violate the semantics of the class. (For example, making a copy of an instance of a class that encapsulates a network connection would lead to chaos if both instances of the class were used interchangeably.)
Problem: How to ensure instances of a C++ class are never copied?
Solution: Declare the copy-constructor and copy assignment operator of the class private and don't implement them. This makes them inaccessible outside the class. If you inadvertently write code that implicitly invokes one of these operations, you will get a link error.
Figure 16 C++ programming idiom
A more popular term for programming idiom today is recipe. Just about every major programming language and platform technology has at least one "cookbook" of recipes for how to accomplish specific tasks using the language or platform (Martelli, et al. 2005) (Margolis 2011) (C. Fowler 2012).
Recipe is a good metaphor because the solutions given are precise step-by-step instructions. The amount of adaptation needed when applying a programming idiom is much less than what is needed when applying a design or architectural pattern.
Another point of distinction between programming idioms and design patterns relates to naming and the existence (or nonexistence) of a vocabulary. Most programming idioms have long awkward names that are a poor basis from which to build a vocabulary. Consider the following representative names: “Trimming space from the ends of a string”, “Adding to or subtracting from a date”, and “Generating URLs dynamically”. Architectural and design pattern names simplify communication. A lot of information is communicated with a statement like: “I’m using the 3-tiered architectural pattern with Row Data Gateway in the data abstraction layer.” It’s hard to imagine names for programming idioms being used in the same way.
A Few Non-Patterns
Not all problem-solution pairs are patterns. If the concept of a pattern is applied too loosely, it could dilute what it means to be a pattern. Although there is no formal criteria or litmus test for what is and isn’t a pattern, there are a few problem-solution pairs that are not generally thought of as patterns.
Algorithms are not design patterns. Algorithms are not design patterns mainly because they have different objectives. The purpose of an algorithm is to solve a specific problem (sorting, searching, etc.) in a computationally efficient way as measured in terms of time and space complexity. The purpose of a design pattern is to organize code in a developmentally efficient way as measured in terms of flexibility, maintainability, reusability, etc.
Algorithms are not design patterns, but some might qualify as programming idioms. A simple algorithm specific to a programming language could be considered a programming idiom.
One-off designs are not patterns. Not every software design rises to the level of a pattern. Patterns are often described as reusable and well-proven solutions. You can’t be sure a one-off design is reusable or well proven. The general rule-of-thumb is a software design can’t be considered a pattern until it has been applied in a real-world solution at least three times (the so called “Rule of Three”). This is why many say that design patterns are discovered rather than invented.
Benefits of Design Patterns
Design patterns generate a lot of interest in the development community. Here are some of the reasons why.
Design patterns facilitate reuse. The most common form of reuse in software development is code reuse. Libraries of reusable components (routines, classes, packages) are made available to programmers for integration (linking) into their applications.
Design patterns enable another form of reuse—design reuse. Practicing design reuse means looking for a routine design before resorting to the creation of an original design.
At design time there are certain design problems that have to be solved such as how to ensure not more than one instance of a class is created or how to attach responsibilities to an object at runtime. When faced with a design problem, designers can search catalogs of design patterns for an existing solution that meets their needs. What is being reused is the structure or organization of the code.
It’s important to note that the solution provided is a design and not code. The design pattern may include sample code, but the purpose of the sample code is to illustrate the design not to be used as is without modification.
Design patterns make design easier. Design patterns make design easier but not easy. Their application still requires a modest amount of reasoning and problem solving.
First, before you can begin to apply design patterns you have to formulate the design problem. Problems solved by mid-level design patterns don't normally show up directly in a requirements document. Requirements are written from the perspective of the problem domain. Design patterns solve problems that arise in the context of the solution domain. Problems solved by mid-level design patterns don't start to show up until you begin to conceptualize solution structures. At best, written requirements might point the way to an architectural pattern (Requirements: “the system shall display inventory data along with controls for updating the data”. You: “hum, seems like a problem suited to the Model-View-Controller architectural style”).
Second, once you have identified a design problem you must be familiar enough with existing patterns to recognize the problem is one for which a design pattern exists. The pattern summaries included with many pattern books (and at the front of this book) can help with the search for a design pattern that addresses a specific design problem (Gamma, et al. 1995) (Buschmann, et al. 1996).
And finally, once identified, it takes skill to adapt the general pattern to fit the problem at hand. Applying design patterns is easier than solving design problems from first principles but their application still requires thoughtful decision making.
Design patterns capture expertise and facilitate its dissemination. Knowledge of design patterns can put you on the fast track to becoming an expert at design. Design patterns capture the body of knowledge that defines our understanding of good design in a way that makes it possible for others to learn from and reuse this knowledge. Experience is still important but design patterns are one of the most effective mechanisms for documenting and transferring design skill and knowledge from one analyst to another.
Design patterns define a shared vocabulary for discussing design and architecture. A common shared vocabulary is necessary for communication. The words in the vocabulary should also be at the appropriate level of abstract for the ideas being communicated. Catalogs of design patterns define a shared vocabulary at the right level of abstraction for efficient communication of design ideas. If one developer says to another “the data is connected to the user interface using the adapter pattern”, the other developer has an understanding of a portion of the design, which includes substantial detail on code structure, intent, characteristics and constraints.
Figure 17 Design patterns provide a vocabulary for discussing design
To the extent that thought is influence by vocabulary (i.e. linguistic determinism), having a high-level vocabulary for discussing design makes it easier to reason about design and architecture. Thinking in terms of low-level artifacts such as declarations and control-flow structures may inhibit design insights and innovations.
Design patterns move software development closer to a well-established engineering discipline. Engineers look for routine solutions before resorting to original problem solving. A prime resource for the data and information engineers need to solve reoccurring problems are engineering handbooks. All established branches of engineering have well-known handbooks that offer practical information for solving reoccurring problems.
Handbooks of design patterns are important references for practicing software engineers. (Software engineers may also want to keep a handbook on refactoring close by when programming.) Having handbooks of reusable solutions to reoccurring design problems moves software engineering a step closer to becoming a well-established engineering discipline.
Studying design patterns improves your ability to do original design. To be an effective designer you must have a solid understanding of fundamental design concepts and principles such as coupling, information hiding and the Open-Closed Principle. These abstract ideas can be difficult (if not impossible) to fully appreciate without experiencing them in context. Design patterns are a natural habitat for design concepts and principles of good design. As you study design patterns your understanding of these concepts and principles and your ability to apply them are likely to improve even if you don’t use the patterns directly.
Knowing popular design patterns makes it easier to learn class libraries that use design patterns. Design patterns are common in programming language class libraries and occasionally show up in the language definition itself (e.g. the foreach statement in PHP is a language implementation of the iterator design pattern). Learning a class library is easier if you are familiar with the design patterns used in the library. For example, the classes and interfaces that make up the Java IO package are confusing to many new Java programmers simply because they aren’t familiar with the decorator design pattern. Programmers that know the decorator design pattern quickly grasp how specific classes in the Java IO package work and know what other types of classes to look for.
Those new to design patterns are often surprised to learn that design patterns aren’t distinguished by their static structure alone. To illustrate, try taking the following design pattern blind comparison test.
Figure 18 shows two class diagrams, one is for the State design pattern and the other is for the Strategy design pattern. Generic names for classes and other identifiers have been used in order to avoid identifying marks that would give away the answer. Can you tell which is which?
Figure 18 Design pattern blind comparison test
It is of course impossible to tell which diagram represents the State design pattern and which represents Strategy. Structurally they are identical.
What makes a design pattern unique is its intent. The intent of a pattern is the problem solved or reason for using it. The intent of the State pattern is to allow an object to alter its behavior when its internal state changes. The intent of the Strategy pattern is to encapsulate different algorithms or behaviors and make them interchangeable from the client’s perspective. The structure is the same for both solutions.
What distinguishes one pattern from another is the problem solved. You can infer the solution structure and problem solved from the pattern name but you can’t infer the pattern name from the solution structure alone.
Design Pattern Templates
One of the most distinctive features of patterns is the uniform structure used to describe them. For example, the patterns introduced earlier were described using a simple structure with just 4 sections: name, context, problem and solution. Having a consistent format makes it easier to learn, compare and use patterns.
There is no single standard format for documenting design patterns. The format used in this book includes 7 sections:
Pattern Name – a short descriptive name. Good names are vital as they form the vocabulary for discussing design.
Introduction – the context for the design problem and motivation for learning the pattern.
Intent – the design problem addressed by the pattern. This is one of the most important sections as it is the trigger for recognizing when there is an opportunity to use the pattern.
Solution – the static structural and dynamic behavioral aspects of the solution. The solution is the essence of the design pattern. Design solutions are typically conveyed using prose and UML class diagrams. UML interaction diagrams are included when collaborations between objects in the pattern are noteworthy.
Sample Code – a code fragment showing an example implementation of the pattern. A design is abstract. Sometimes it is easier to understand an abstract concept by inferring the abstraction from a tangible example.
Discussion – additional information about the pattern. In order to keep the other sections as concise as possible, secondary material on the pattern is presented in this section. Think of this section as extra for experts.
Related Patterns – patterns related to the one being described. Patterns can be related for different reasons. One pattern may fully contain another (Model-View-Controller contains Observer). One pattern may address a problem that is similar to a problem addressed by another pattern (Template Method and Strategy address similar problems). One pattern may have the same structure as another (Strategy has the same structure as State). Knowing the relationships between patterns helps in understanding the subtle distinctions between the patterns.
Introduction to the Patterns
Ten design patterns are presented here:
7. Factory Method
10. Template Method
I chose these patterns because they are the ones used most often in practice.