9.6 Design Patterns
Definition and Structure of a Design Pattern
A reusable design must include more information than just the design diagrams. Among other things, the problem that the design is meant to address must be defined. This information is important because it allows potential reusers, faced with a particular problems, to identify available designs that are candidates for reuse. Another kind of information that is needed in a reusable design cncerns the trade-offs implied by the design. Typically, designers must achieve a balance between such competing goals as efficiency, flexibility, fault tolerance, and simplicity. A single problem may give rise to several useful designs, each offering a different balance among the design factors.
A design pattern is a proposed format for presenting a reusable design. The structure of a design pattern is defined in the book Design Patterns by Gamma, Helm, Johnson and Vlissides (360) as:
The first sentence of the definition expresses the intent of a design pattern: to present in a consistent and coherent manner the solution to a recurring design problem. The next two sentences of the definition outline the content of a design pattern. The last two sentences explain the usage of a design pattern. The usage makes clear that a design pattern is not a program or code; it is a design that must be tailored to fit the specific requirements of a particular problem and then implemented. Design Patterns contains a collection of design patterns, one of which is studied in detail here.
A design pattern includes the following twelve elements:
An Example of a Design Pattern
The intent expresses both the subject matter of the pattern (trees of objects related by a part-whole relation) as well as the goal of the pattern (uniformity of treatment). The motivation presented for this pattern describes a typical graphical drawing tool that allows users to draw a variety of predefined basic shapes (rectangles, lines, circles, rounded rectangles, polygons, etc.) and also allows shapes to be composed together. A composite shape is treated as a newly defined basic shape in that it can be composed with other basic or composite shapes. A set of operations (draw, move, resize, etc.) can be applied to any shape (basic or composite). Both a class diagram and an object diagram are used to illustrate a design solution. The class diagram is shown in the figure below.
The participants section lists the responsibilities of each of the four classes that appear in the structure (Component, Leaf, Composite, Client). For example, the responsibilities of the Composite class are to define the behavior of "a composition of objects, to store the composed (sub)objects, and to implement the operations defined in the abstract base class."
The collaborations section defines how the Composite pattern's participants work together to meet their individual responsibilities and achieve the effect intended for the pattern. For instance, in this example, in the Composite pattern, the client interacts with the Leaf or Composite objects only through the abstract interface defined in the Component class. This collaboration captures a key feature of the pattern: the client is able to manipulate Components without regard for whether they are Leaf class objects or Composite class objects.
Two of the consequences of using the Composite pattern are that it makes it easy to add new kinds of basic components (i.e., Leaf classes) - they are simply added as another class derived from Component - and it can make the design overly general in those cases where only certain combinations of objects have semantic meaning (for example, a document may be viewed as a Composite of paragraphs, tables, sections, etc. However, a correct document must have exactly one title and a table may not have sections within it. It is difficult to enforce these kinds of restrictions with the Composite pattern as the pattern places no limitations on the way in which a Composite can be formed. Notice again that both the strengths and the limitations of the pattern are identified.
The implementation section presents issues relevant to the detailed coding of the classes in the pattern. For example, the trade-off between safety and transparency is considered in this section of the Composite pattern. This trade-off involves where to place the methods for manipulating the children of a Composite. If these methods are placed only in the Composite class, it is safer (because any attempt to apply them to Leaf components is detected as a compile-time error) but it is also less transparent (because Leaf and Composite objects cannot be treated as uniformly as might be desired). Placing the methods in the Component base class yields the opposite trade-off. A compromise strategy introduces a method Composite* Get Composite() in the Component base class. This method is defined in the Leaf derived class to return a null pointer and defined in the Composite derived class to return its this pointer. This strategy minimizes the loss of transparency, as all base class methods apply equally to both Leaf and Composite objects, while it retains a large measure of safety by leaving to the Client the responsibility to differentiation between objects of the two derived classes.
The sample code section gives C++ code for an example of a part-whole problem. This most detailed level of presentation helps to give a concrete representation of the pattern that can be compiled and used for experimentation.
The known uses section lists three application domains (user interface toolkits, a compiler, and financial portfolios) where the pattern has been observed.
Finally, four related patterns are noted, including the Iterator pattern that can be used to traverse composite structures.
A design pattern is a means of fostering reuse of design knowledge and experience rather than the reuse of a specific implementation. A design pattern falls between a general design heuristic and actual code: it is less general (more limited) than a design heuristic or guideline, which might express, for example, that a class interface should be a coherent and complete set of methods for an abstraction. While heuristics are intended to apply to the broadest range of cases, a design pattern is intended to capture in more detail a single structure or abstraction. At the same time, a design pattern is more general than an implemented class hierarchy or framework. Hierarchies and frameworks are limited in their reuse potential to a single programming language, a single (or small range) of situations, and perhaps even a single operating system or run-time environment. A design pattern, however, has none of these limitations. A design pattern is sufficiently focused to be useful, but general enough to be applicable in a range of applications.
The ultimate value of design patterns will only be realized by an organized, extensive collection of patterns that encompasses generic design problems as well as problems specific to particular applications domains. One might imagine a collection of patterns for real-time systems, distributed/concurrent/parallel applications, and other important application areas. The collection of patterns in Design Patterns is an important beginning.