Contents
Chapter 21

The Pattern Concept

An important step forward in object-oriented design was the “design patterns” movement, carried into the mainstream by the 1994 book Design Patterns by Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides. They became known as the “Gang of Four”1. I will refer to that book as GoF Design Patterns, and use design patterns for the concept.

GoF Design Patterns shows 23 different solutions to particular classes of problems, along with one or more examples for each, typically in C++ but sometimes in Smalltalk. A significant portion of those examples provides inspiration for much of this part of the book. I introduce the basic concepts of design patterns, along with examples.

What Is a Pattern?

Initially, you can think of a pattern as an especially clever and insightful way of solving a particular class of problems. Many people have worked out all the angles of a problem and have come up with the most general, flexible solution. You may have seen and solved something like it before, but your solution probably doesn’t have the kind of completeness you’ll see embodied in a pattern.

That completeness has a failure mode. Once you know a catalog of patterns, it is tempting to treat it as a checklist, and to install patterns as proof of sophistication. A pattern earns its place only when the problem it solves is actually present. If nothing varies, you do not need machinery for isolating variation. A pattern without its problem is just overhead.

Although they’re called “design patterns,” they aren’t tied to the realm of design. Patterns seem to stand apart from the traditional way of thinking about analysis, design, and implementation. Instead, a pattern embodies a complete idea within a program. Thus it can sometimes appear at the analysis phase or high-level design phase. Because a pattern has a direct implementation in code, you might expect it to appear no earlier than low-level design. It appears at every level, and you often discover that you need one only once you reach the code.

The basic concept of a pattern is also the basic concept of program design: adding a layer of abstraction. Whenever you abstract something, you isolate particular details. One of the most compelling motivations behind this is to separate things that change from things that stay the same. Once you find a part of your program that’s likely to change, patterns can prevent those changes from causing secondary effects throughout your code. This makes the code cheaper to maintain and usually simpler to understand.

Often, the most difficult part of developing an elegant and cheap-to-maintain design is discovering what I call “the vector of change” (here, “vector” means a direction of change, not an array of numbers). This means finding the most important thing that changes in your system, which points to your greatest cost. Once you discover the vector of change, you have the focal point around which to structure your design.

Notice that a vector of change is discovered, not predicted. Guessing at it up front often builds flexibility in a direction that doesn’t get used. This creates complexity to produce generality that never pays off. Let real changes reveal it. The second time a requirement shifts the same part of the design, you have evidence.

The goal of design patterns is to isolate changes in your code. You’ve already seen some design patterns in this book. For example, inheritance can be thought of as a design pattern (albeit one built into the language). It allows you to express differences in behavior (that’s the thing that changes) in objects that all have the same interface (that’s what stays the same). Composition also qualifies as a pattern, since it allows you to change, dynamically or statically, the objects that implement your class, and thus the way that class works.

Another pattern that appears in GoF Design Patterns is the Iterator, which has been implicitly available in for loops from the beginning of the language, and became an explicit feature in Python 2.2. An iterator allows you to hide the particular implementation of the container as you’re stepping through it. You can write generic code that performs an operation on all of the elements in a sequence without regard to the sequence’s construction. Generic code can work with any object that produces an iterator.

A pattern is often a sign of something missing in a language. Enough programmers wrote the same scaffolding often enough to name it. That scaffolding exists only because the language would not write it for them. When a language later absorbs the feature, the pattern dissolves into it2. Python has absorbed several. Iterator became the machinery of the for loop, and Strategy and Command shrink to passing a function (Function Objects shows both). This is why the chapters ahead keep asking the question Rethinking Objects posed: how much of each pattern’s machinery does Python still need, and how much dissolves into functions, data, and protocols?

Pattern Evolution

A pattern arrives in stages, each more general than the last:

  1. Idiom: how we write code in a particular language to do this particular type of thing. This could be something as common as the way that you code the process of stepping through an array in C (and not running off the end).
  2. Specific Design: the solution that arose to solve this particular problem. This might be a clever design, but it makes no attempt to be general.
  3. Standard Design: a way to solve every problem of that kind, not just the one in front of you. A design that has become more general, typically through reuse.
  4. Design Pattern: how to solve an entire class of similar problems. This usually only appears after applying a standard design a number of times, and then seeing a common pattern throughout these applications.

In Python terms: with open(...) for guaranteed cleanup is an idiom, stage one, meaningless outside a language that provides with. Template Method is a design pattern, stage four: a shape of solution you could build in any language with polymorphism.

This progression doesn’t say that one stage is better than another. It doesn’t make sense to try to take every problem solution and generalize it to a design pattern. You can’t force the discovery of patterns that way. They tend to be subtle and appear over time.

Pattern Taxonomy

GoF Design Patterns discusses 23 different patterns, classified under three purposes (all of which revolve around the particular aspect that can vary). The three purposes are:

  1. Creational: how to create an object. By isolating the details of object creation, your code isn’t dependent on what types of objects there are and thus won’t change when you add a new type of object. Singleton counts as a creational pattern, and later in this book you’ll see examples of factories.
  2. Structural: designing objects to satisfy particular project constraints. How objects connect with other objects to ensure that changes in the system don’t require changes to those connections.
  3. Behavioral: objects that handle particular types of actions within a program. These encapsulate processes such as interpreting a language, fulfilling a request, moving through a sequence (as in an iterator), or implementing an algorithm. This book contains multiple examples including Observer, State Machines, and Visitor.

I’ve found the GoF Design Patterns classification to be too obscure, and not always helpful. Certainly, the Creational patterns are fairly straightforward. How will you create objects? This is a normal question, and the name brings you right to that group of patterns. But I find Structural and Behavioral to be far less useful distinctions. I have not been able to look at a problem and say “clearly, you need a structural pattern here,” so that classification doesn’t lead me to a solution (I’ll readily admit that I may be missing something here).

Patterns often resemble each other more in their implementations than the GoF Design Patterns categories suggest, and that is how this book groups them. Surrogate treats Proxy and State as one front-object structure. Function Objects treats Command, Strategy, and Chain of Responsibility as one function-passing structure. Composite and Interpreter treats both of its patterns as one recursive-data structure. When two patterns share a structure, learning one teaches you most of the other, and the remaining difference is intent.

Design Principles

Design principles are at least as important as design structures, but for a different reason. Principles ask questions about your proposed design, to apply tests for quality. Most hold for any code. Reflexivity and the Law of Demeter assume classes and objects.

This is a small handful of fundamental ideas that you can hold in your head while walking through and analyzing your design.