Getting Started with Patterns
February, 1998
This is a draft copy of an article that appeared in the February 1998 edition of Software Development magazine. It may be freely reproduced and distributed provided the following copyright notice stays intact.
Suppose I was to tell you about a wonderful new technology, so powerful that it would dramatically cut your development times, substantially improve software quality, and reduce maintenance costs to less than one-third their present levels. What would you do?
I hope you'd laugh in my face, telling me that I was full of a certain substance whose name is not fit for printing in this fine magazine.
But what if I told you about a new trend in software development that captures and shares best practices in an innovative new way? And what if I told you that this method found its success in helping you apply your skill and experience to solving problems in a way that builds upon the proven solutions of others? Would you be interested in hearing more?
Nothing Breeds Success Like Success
The trend that I am writing about is the software patterns movement, which has its roots in the ground-breaking architectural design work conducted by Christopher Alexander. A pattern is a solution to a problem in a context. Patterns aren't fragments of well-documented source code (although they may contain source code), but instead a literary form that documents the problem, the context in which it occurs, and a good solution in a compact form. What makes patterns unique, and distinctly different from other technologies that compete for your attention, is that they embody wisdom about how the solution addresses the problem. It is this wisdom, the embodiment of the proven solution to a common problem, that makes patterns so incredibly powerful.
Suppose, for example, that you are building a system with a graphical user interface and want to provide for the undo and redo of commands as well as the logging of commands (for debugging or macro support purposes). One way to solve this is to engage in a traditional, object-oriented analysis and design of the problem, and then implementing your design. In the process, you'll probably find that you need to resolve several important issues:
- How should commands be represented?
- Who should be responsible for managing commands?
- How do you distinguish between commands that can be "undone" and those that can't?
Unfortunately, resolving each of these issues chews up that one absolutely critical development resource: time.
Another way to solve this problem is by building upon the experience of numerous other designers and using the Command Processor pattern, which describes how to separate service requests (commands) from the management and execution [Vlissides et. al. 1997:63–74]. This pattern not only describes the answers to the above questions (commands should be represented as objects; a CommandProcessor class manages commands, including deleting them when they are no longer needed; use a state machine to distinguish between commands that can be "undone" from those that can't) it also highlights several other potential design and implementation "gotchas" (e.g., use two stacks to implement unlimited undo & redo; use the Composite pattern to implement macro capabilities, etc.).
Note that the Command Processor pattern is not a piece of code wrapped inside a library that you simply link into your application. Instead, it is a pattern that describes, at a much more abstract level, how to solve the problem. Using it, and any other pattern, requires you to apply your skill and intelligence in tailoring it to meet the specific needs of your problem. Command Processor provides guidance on how to create the solution; actually creating the solution is in your hands. It be used thousands of times, with each idiosyncratic implementation true to the pattern but distinctly unique.
A second key benefit of patterns concerns the broader and deeper issues by which we design systems. Most system design work can be characterized as trying to manage complexity through commonality and variability analysis [Coplien 1997]. Relational databases, for example, rely on the rules of normalization to determine what is common and what is variable, while object-oriented analysis and design is based on the creation of specific taxonomies of classes related through aggregation and inheritance. But what happens when this kind of commonality and variability analysis breaks down, such as when you want to structure the interaction between a group of objects in a meaningful way? Patterns provide a means of capturing and sharing the interactions between objects in ways that go beyond commonality and variability analysis.
A third benefit of patterns is their applicability to many different problem domains. Because patterns concentrate on proven solutions to recurring problems in specific contexts, they can (and have) been used to describe many different kinds of problems. A sample of patterns and their applicable domains include:
- Caterpillar's Fate, which describes the processes one should follow in the development of concurrent systems;
- Crossing Chasms, a pattern language that describes how to integrate object-based systems with relational databases;
- A Generative Development-Process Pattern Language, a pattern language that provides guidance for structuring projects based on studies of successful software projects; and,
- Reactor, a pattern that describes how to build concurrent systems.
Note: Other patterns can be found at http://hillside.net/patterns.
The key point here is that that patterns aren't just a single-point technology solution, like a different way to organize a database for more efficiency analytical processing. Instead, patterns are a literary form designed to convey wisdom. This form has surprising versatility.
A final benefit of patterns is the way they create a shared language for reasoning about solutions to problems. Consider two designers skilled in the use of design patterns. Instead of talking at a detailed level about two different design decisions, they can instead discuss alternatives at the higher-level abstractions provided by patterns, because each of them shares the same relative understanding of the solution.
What's In A Pattern?
In a way, patterns are an extremely sophisticated form of documentation. Although the specific structure of a pattern may vary, patterns convey the following information. (Certain of the literary forms that are used to structure patterns make these sections explicit).
| Name | A name that captures the meaning or intent of the pattern in a way that facilitates communication. |
| Intent | A single sentence that summarizes what the pattern does, describing the design issue or problem it addresses. |
| Problem | A description of the problem it solves. Note that two different patterns may have a similar intent, but solve slightly different variations of the same problem. This intent is often founded on the deep principles of well-crafted software (low coupling, high cohesion, encapsulation, and so forth). |
| Context | The context provides a means of comparing the specific forces that characterize your problem to the intent of the pattern in a manner that allows you to make a wise decision on the use of the pattern. For example, the Flyweight pattern uses sharing to support large numbers of fine-grained objects efficiently. However, the applicability of the Flyweight is based on a number of forces that can only be found in the context of the application. For example, you can't use Flyweight if each object must maintain a unique identity or has a unique state that cannot be made extrinsic. |
| Forces | Forces guide you in the specific implementation of the pattern. I like to think of the forces as a means of ensuring that the solution I'm about to implement creates a foundation that not only reduces entropy now, but works to keep entropy low as the system evolves over time. |
| Solution | Ultimately, patterns need to deliver on a solution. The description is usually described to address the broadest possible context, but with enough detail that you can solve the problem directly. Patterns that deal with coding issues, for example, will often include code fragments to help ensure the meaning of the solution makes good sense. |
| Sample | Many patterns include a sample of how the pattern was used to solve a specific problem as means of illustrating how to use the pattern. |
| Consequences | Also known as the resulting context, this section concludes the pattern by describing the expected state of the system after applying the pattern. In a pattern language (see the sidebar "Pattern Taxonomies") the resulting context leads you through the successive application of different patterns to engineer a robust solution that effectively address all of the complexity of the problem (and the new problems introduced by a specific solution). |
Good pattern writers balance the amount of information that is conveyed with the length of the document. More specifically, a good pattern ranges from one to four pages in length; brevity is preferred. Like yourself, pattern writers are often busy developers. Writing a novel isn't necessary to convey the information necessary to describe and solve a problem.
One important rule associated with patterns is the rule of three: Unless the pattern has been used in at least three systems it is rarely considered a pattern. (That doesn't mean novel solutions aren't worthy of precise documentation or sharing, for they may represent the heart of an important, but specialized, business algorithm. However, such solutions don't adhere to the natural meaning of the word "pattern".). Thus, many pattern writers introduce (or close) a pattern by describing the systems that have implemented the pattern successfully. In this manner skeptical developers can judge for themselves the "believability" factor of the pattern in their work.
| Sidebar: Pattern Taxonomies |
Patterns are certainly a hot topic, with several books and papers being published in the last few years.
At times this has caused a bit of confusion, as different people have used slightly different names for different pattern
concepts. Fortunately, a reasonable consensus seems to have been reached on the following taxonomy of patterns.
Beyond this simple taxonomy there are numerous other patterns and pattern languages. (A pattern language is a collection of patterns that work together to resolve a complex problem in a generative manner. The "generativity" of a pattern language refers to the idea that solving a problem through the use of one pattern usually introduces a different problem. A pattern language is organized so that the successive application of patterns resolves the problems that arise in the overall development of the system, with the most difficult problems being addressed first. Caterpillar's Fate, mentioned earlier, is one such pattern language.) |
Identifying And Using Patterns
If you have read this far into the article, I suspect that you are at least interested in seeing what patterns can do you for. More to the point, if you don't already use patterns, how can you get started?
There are many ways to become familiar with and start using patterns. One way is browse through some of the web sites devoted to patterns (see the sidebar "Pattern Resources and References") and peruse the online articles. Another is to purchase a book on patterns. For architects, I recommend POSA and Shaw. For database designers, I recommend Hays or Fowler. For developers involved in object-oriented technologies, there are many excellent books. The classic is Design Patterns or the books published from the Pattern Languages of Program Design conferences.
Of course, reading a book on patterns won't necessarily give you skill and experience in applying them. Actually using patterns successfully in your work requires a bit more effort. First, you must have clarity on the problem you are trying to solve. I've found that stating the problem, in a concrete manner, using one or two sentences, helps a great deal. You should have a broad understanding of the context in which the problem is situated, and the forces in the system that influence your decision (e.g., are you more interested in maintainability or efficiency? do you have three developers or seven? is the context stable or is it likely to change?).
Second, you must be willing to ask yourself if others may have faced this (or a similar) problem in the past and if you are willing to search for and apply their solution should one be found. Unfortunately, for numerous reasons this step is harder than it sounds. Developers are often wary of solutions developed by others, and may be skeptical of design patterns. (Taken to extremes, this is the classic "Not Invented Here" response to a solution.) Developers also face severe time constraints, and searching for the right pattern may not appear to be a luxury they can afford. But, if you are facing a problem that you think may have been solved before, you certainly owe it to yourself (and your fellow developers) to look for a pattern-based solution.
The third key element is the ability to abstract the specific context of your problem. I've found that restating your problem in an abstract manner helps you move from the specific requirements of your problem into the more abstract realm of patterns. One way of doing this is to change the names of the classes in your concrete problem to more generic names that are reflective of an abstract problem. Another is to try and imagine that you are trying to solve this problem for a slightly different system. A third approach is to think of the problem only in terms of abstract interfaces.
To illustrate, suppose you have been given the task of writing "glue" code to connect a C++ system to a set of Fortran libraries. The specific problem might be: "How can I create a C++ object for Susan and Ramir that helps them use the Fortran SIGNLLIB library for signal processing?". The abstract problem might be: "How can I provide a means of adapting one system to use a set of features and functioned provided by another?" Another way of asking this question abstractly might be: "How can I create a subsystem that provides a simple and cohesive interface to another, more complex subsystem?"
If you are familiar with the patterns described in the book Design Patterns, you've probably identified two good candidates for solving this problem: Adaptor, which provides guidance on how to adapt one interface for use by another, and Façade, which provides guidance on creating simplified interfaces to complex subsystems. The specific pattern chosen would depend on the specific forces presented by the original problem. For example, Adaptor is more appropriate when you want to simply adapt object interfaces, while Façade is a better choice when part of the design goal is to simplify the interface to the subsystem. Suppose that the Fortran library required fairly elaborate initialization procedures, and you want to hide these details from your fellow developers. In this case you would probably choose Façade, and implement the Façade in such a way as to minimize initialization complexity.
It is important to note that forming and asking abstract questions is a critically important step to using patterns effectively. Different forms of the same abstract question may yield slightly different results. As a result, the best abstract questions take into account the surrounding context of the original problem. Over time, you will become more effective at forming these abstract questions. Once you have identified the right pattern, you will find that the combination of your specific problem statement and the pattern itself will help guide you to a good solution.
I must stress that there is no guarantee that you will find a pattern that solves your problem. This is not a failing of patterns, but instead the realization that it simply does not make sense to capture every solution in pattern form. Remember, patterns are designed to capture the proven solutions to commonly recurring design problems, not the solution to every design problem! When this happens you will find that you must resort to more general principles of system design to solve your problem (information hiding, loosely coupled, highly cohesive components, stable interfaces, separation of concerns, and so forth).
Patterns Aren't A Silver Bullet!
Although I am an enthusiastic supporter of patterns, I would be misleading you if I were to give you the impression that patterns were some sort of "silver bullet" that solve every problem in software development. Fundamentally, great software is written by adhering first and foremost to the timeless principles of software design: information hiding, encapsulation, well-defined, semantically meaningful interfaces, source code with variables and formatting chosen with care, and so forth. Patterns are not a replacement for knowing how to apply these principles as a professional software developer.
Coupling, for example, is a measurement of the degree of interconnectedness between two components. It isn't a pattern. And principles, such as low coupling, aren't patterns. But, the techniques we use to achieve low coupling can be captured as patterns. By capturing these techniques as patterns we can make explicit the design decisions that must be made in realizing a specific lower coupling (sometimes the right design decision is to increase coupling; as always, the best decision depends on the specific context and forces of the problem you are trying to solve).
Another problem with patterns is pattern explosion. As more and more developers discover patterns and begin to use the pattern form to document and share their knowledge, the number of published patterns continue to grow. I'm concerned that too many patterns may overwhelm you—and me! Choosing among the 23 different patterns in the Design Patterns book can be pretty hard. How quickly and effectively could we choose between 230? I believe that over time we will continue to see the development of domain-specific pattern languages that provide a level of organization over collections of patterns. In this manner the pattern language will become your entry point into choosing the right patterns to address your specific problems.
A deeper problem concerns the misuse of patterns. Specifically, I've witnessed developers striving to force-fit patterns to their problems. Patterns are most effective when the feel as if they resolve your problem in a relatively seamless manner. The overall entropy of the system decreases, and there is a feeling of greater calmness in the resulting implementation.
I have noticed that one perceived problem with using patterns in C++ is that solutions based on patterns usually contain more classes than their non-pattern counterparts. This is a direct result of enhanced encapsulation and the use of abstract base classes to specify stable interfaces. Unfortunately, it appears that many of our fellow developers consider adding extra classes in C++ painful. Our response? Get over it! If a design pattern is the right way to solve the problem, use it.
(To be fair, adding a class in C++ is a bit of a pain: you have to create a header file and an implementation file, add it to your makefile, and so forth. The time it takes to do this in support of a pattern will be won back many times over through enhanced maintainability and understandability.)
A final problem with patterns concerns the larger of community of developers in which you work. Are you the only member of your team who knows or uses design patterns? If this is true you may think that patterns actually detract from teamwork. For example, you may find you fellow developers are mystified by your solutions, and that you haven't yet found the advantages of shared design vocabulary we discussed earlier. This is a real problem: What makes patterns so powerful also makes them inaccessible for the uninitiated. If you find yourself in this situation I encourage you to organize a series of "brown bag" lunches where they meet to discuss patterns and how they can be used.
Summary
I'm willing to bet that you and I share many fundamental ideas about software development. For example, I'm willing to bet that your development teams, like my own, are asked to bring products to market faster, with higher quality, and with enhanced long term maintenance characteristics. As such, I've learned to be quite skeptical about the claims associated with new development technologies. Quite simply, many new technologies fail to deliver on their promise.
I've found that patterns do represent a significant breakthrough in improving software development practices. They do this by valuing and enhancing the most powerful tool that exists in problem solving: your mind. Specifically, patterns don't promise to automate the creation of source code from specifications or diagrams. They aren't component-level reuse. Instead, they represent the wisdom of other developers captured in a literary form designed to share that wisdom efficiently and effectively.
At SmartPatents, we use numerous patterns in the development of our software systems. For example, of the 23 patterns described in the Design Patterns book we've used five of the six creational patterns, four of the seven structural patterns, and nine of the eleven behavioral patterns. We've used all of the patterns in Craig Larman's GRASP pattern language, as well as several patterns from the Pattern Languages of Program Design books. I'm confident that we have saved thousands of dollars and have dramatically improved the quality of our source code by building upon the proven solutions of others. I'm equally confident that you will find similar value in your own developments through the use and adoption of patterns.
| Sidebar: Pattern Resources and References |
| There are a lot of resources available to the person wishing to
learn more about patterns. I'll begin with the Internet. http://st-www.cs.uiuc.edu/users/patterns/patterns.html
There are two mailing lists devoted to software patterns, patterns and patterns-discussion. To subscribe to any of them simply send a message to the following addresses with "subscribe" in the subject.
There are numerous books about patterns. Here I'll describe the ones that I've found to have great value. Your mileage may vary, but you should consider several of them.
Hay, C. D. Data Model Patterns: Convention of Thought. Dorset House
Publishing, 1995.
Gamma, E., R. Helm, R. Johnson, and J. Vlissides. Design Patterns: Elements of Reusable Object-Oriented Software. Addison Wesley. October 1994.
Larman, C. Applying UML and Patterns : An Introduction to Object-Oriented Analysis and Design. Prentice-Hall, 1997.
Pattern Languages of Program Design, 1. J. Coplien and D. Schmidt,
eds.
Buschmann, F., R. Meunier, H. Rohnert, P. Sommerlad, and M. Stal. Pattern-Oriented Software Architecture - A System of Patterns. Frank Wiley and Sons Ltd., 1996
Alexander, C. The Timeless Way of Building. Oxford University Press,
1979.
|
Home