Item 3. Design Patterns
Anyone who is not already familiar with design patterns may, after a brief survey of the field, come away with the impression that design patterns are a lot of marketing hype, are just some simple coding techniques, or are the playthings of computer scientists who really should get out more. While each of these impressions carries a grain of truth, design patterns are an essential component of the professional C++ programmer's toolkit.
A "design pattern" is a recurring architectural theme that provides a solution to a common design problem within a particular context and describes the consequences of this solution. A design pattern is more than a simple description of a technique; it's a named capsule of design wisdom gleaned from successful existing practice, written in such a way that it can be easily communicated and reused. Patterns are about programmer to programmer communication.
From a practical perspective, design patterns have two important properties. First, they describe proven, successful design techniques that can be customized in a context-dependent way to new design situations. Second, and perhaps more important, mentioning the application of a particular pattern serves to document not only the technique that is applied but also the reasons for its application and the effect of having applied it.
This sort of thing is nothing new. Consider an analogy from the field of algorithms. (Algorithms are not design patterns, and they're not "code patterns." They're algorithms, and this is an analogy.) Consider the following statement that I might make to a colleague: "I have an unsorted sequence that I have to search a number of times. Therefore, I'm going to quick sort it and use binary search to perform each lookup." The ability to use the terms "quick sort" and "binary search" is of inestimable value not only in design but also in communicating that design to an educated colleague. When I say "quick sort," my colleague knows that the sequence I'm sorting is in a random access structure, that it will probably be sorted within O(nlg2n) time, and that the elements in the sequence may be compared with a less-than-like operator. When I say "binary search," my colleague knows (even if I hadn't earlier mentioned "quick sort") that the sequence is sorted, that I will locate the item of interest within O(lg2n) comparisons, and that an appropriate operation is available to compare sequence elements. Shared knowledge of, and a standard vocabulary for, standard algorithms permits not only efficient documentation but also efficient criticism. For example, if I planned to perform this search and sort procedure on a singly linked list structure, my colleague would immediately smirk and point out that I couldn't use quick sort and probably wouldn't want to use binary search.
Until the advent of design patterns, we missed these advantages in documentation, communication, and efficient smirking with our object-oriented designs. We were forced into low-level descriptions of our designs, with all the inefficiency and imprecision that entails. It's not that techniques for sophisticated object-oriented design didn't exist; it's that the techniques were not readily available to the entire programming community under a shared, common terminology. Design patterns address that problem, and we can now describe object-oriented designs as efficiently and unambiguously as algorithmic designs.
For example, when we see that the Bridge pattern has been applied to a design, we know that at a simple mechanical level an abstract data type implementation has been separated into an interface class and an implementation class. Additionally, we know that the reason this was done was to separate strongly the interface from the implementation so that changes to the implementation would not affect users of the interface. We also know a runtime cost exists for this separation, how the source code for the abstract data type should be arranged, and many other details. A pattern name is an efficient, unambiguous handle to a wealth of information and experience about a technique, and careful, accurate use of patterns and pattern terminology in design and documentation clarifies code and designs.
Patterns wonks sometimes describe design patterns as a form of literature (they really do) that follows a certain formal structure. Several common variants are in use, but all forms contain four essential parts.
First, a design pattern must have an unambiguous name. For example, the term "wrapper" is useless for a design pattern, because it is already in common use and has dozens of meanings. Using a term like "Wrapper" as a pattern name would lead only to confusion and misunderstanding. Instead, the different design techniques that formerly went under the name "wrapper" are now designated by the pattern names "Bridge," "Strategy," "Façade," "Object Adapter," and probably several others. Use of a precise pattern name has a clear advantage over using a less precise term, in the same way that "binary search" is a more precise and useful term than "lookup."
Second, the pattern description must define the problem addressed by the pattern. This description may be relatively broad or narrow.
Third, the pattern description describes the problem's solution. Depending on the statement of the problem, the solution may be rather high level or relatively low level, but it should still be general enough to customize according to the various contexts in which the problem may occur.
Fourth, the pattern description describes the consequences of applying the pattern to the context. How has the context changed for better or worse after application of the pattern?
Will knowledge of patterns make a bad designer a good designer? Time for another analogy: Consider one of those painful mathematics courses you may have been forced to undergo, in which the final examination is to prove a number of theorems in a certain area of mathematics. How do you get out of such a course alive? One obvious way is to be a genius. Starting from first principles, you develop the underpinnings of an entire branch of mathematics and eventually prove the theorems. A more practical approach would be to memorize and internalize a large number of theorems in that area of mathematics and use whatever native mathematical ability, inspiration, or good luck you have at your disposal to select the appropriate subsidiary theorems and combine them with some logical glue to prove the new theorems. This approach is advantageous even for our fictitious genius, because a proof built upon established theorems is more efficient to construct and easier to communicate to mere mortals. Familiarity with subsidiary theorems does not, of course, guarantee that a poor mathematician will be able to pass the test, but such knowledge will at least enable that person to understand the proof once it has been produced.
In a similar vein, developing a complex object-oriented design from first principles is probably going to be tedious, and communication of the eventual design difficult. Composition of design patterns to produce an object-oriented design is similar to use of subsidiary theorems in mathematics to prove a new theorem. Design patterns are often described as "micro-architectures" that can be composed with other patterns to produce a new architecture. Of course, selecting appropriate patterns and composing them effectively requires design expertise and native ability. However, even your manager will be able to understand the completed design if he or she has the requisite knowledge of patterns.