A Survey of Common Design Patterns
If you are new to design patterns, studying them can seem like a daunting task. The number and scope of design patterns has been steadily increasing since the publication of Design Patterns: Elements of Reusable Object-Oriented Software, by Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides in 1995. In their book, they present 23 design patterns organized into three categories: 1) Creational, 2) Structural, and 3) Behavioral design patterns. Since then, they and other authors have described other classification schemes for these patterns. By examining design patterns within different classification strategies, you will give yourself a deeper insight into what they are and how to use them. As you gain familiarity with them, you can begin to use them in your projects. In this paper, we will summarize the 23 common design patterns within the three categories. It is intended to serve as a glossary that I will refer to in the future.
The Abstract Factory is intended to provide a single interface for clients to use when they need to create a family of related objects without having to specify concrete classes. For example, imagine a system that needs to implement platform-specific user interface objects (menus, toolbars, and so forth) for several different platforms. Abstract Factory simplifies the interface for the client by localizing all of the initialization strategies within a single class, the Abstract Factory. The pattern works to ensure that all the strategies can work together correctly.
To understand Abstract Factory, examine the class diagram shown in the following figure. Note that there are two separate hierarchies. The first represents the various abstractions the client has interest in. For each abstraction, there exists an abstract class definition (AbstractClass1, AbstractClass2, and so on), and the subclasses that provide the concrete implementations (ConcreteClass1A, ConcreteClass1B, and so forth). In the second hierarchy, an abstract AbstractFactory class is defined to provide the interface for each class that is responsible for creating the members of a particular family. For example, ConcreteFactoryB is responsible for creating objects from classes ConcreteClass1B, ConcreteClass2B, and the like. All the client needs to worry about is which family of objects it is interested in creating and calling the appropriate Factory method. Because the client only knows about the abstract interface, it can interact with objects from different families without having to know about their internal implementation details. This approach has the benefit that the family can be allowed to grow without the need to modify the client. The primary drawback of AbstractFactory is that it can limit your flexibility when you want to take advantage of a specific capability a family may have. This is because you must provide a common interface across all families, even if some of them do not have this capability.
The Adapter is intended to provide a way for a client to use an object whose interface is different from the one expected by the client, without having to modify either. This pattern is suitable for solving issues that arise, for example, when: 1) you want to replace one class with another and the interfaces do not match, and 2) you want to create a class that can interact with other classes without knowing their interfaces at design time.
In the class diagram shown for Adapter in the following figure, the client wants to make a specific request of an object (Adaptee). Ordinarily, this would be accomplished by creating an instance of Adaptee and invoking SpecificRequest(...). In this case, however, the client cannot do so because the interfaces do not match (the parameter types are different). Adapter helps you address this incompatibility by providing classes that are responsible for redefining the data types on the client's behalf. First, you create a custom Target class that defines methods using an interface expected by the client. And second, you create a custom Adapter that implements the interface expected by the Adaptee. The Adapter class subclasses the Target, and provides an alternate implementation of the client requests in terms that the Adaptee expects. Adapter overrides the Target method and provides the correct interface for Adaptee. This approach has the advantage that it does not lead to modifications in the client. Adapter is a structural pattern and you can use it to react to changes in class interfaces as your system evolves, or you can proactively use the Adapter pattern to build systems that anticipate changing structural details.
The Bridge is intended to decouple an abstraction from its implementation so both can vary independently. For example, a significant maintenance headache entails the coupling that occurs between custom classes and the class libraries they use. Bridges are useful for minimizing this coupling by providing an abstract description of the class libraries to the client. For example, a typical abstraction could be created for displaying a record set (DisplayRecordset as an Abstraction). The DisplayRecordset class could then be implemented using a variety of user interface elements (data grids, data lists, etc.), or with different formats (chart, table, and so forth). This has the effect of keeping the client interface constant while you are permitted to vary how abstractions are implemented. In addition, you can localize the logic used to determine which implementation and which abstraction the client object should use. The remaining client methods only need to know about the abstraction.
To illustrate the Bridge Pattern, consider the class diagram displayed in the following figure. The client would have a method such as "SetupAbstraction(...)" that would be responsible for determining which implementation to use for the abstraction. Then the client is free to interact with the Abstraction and the proper implementation class will respond.
Builder is used to separate the construction of complex objects from their representation so the construction process can be used to create different representations. The construction logic is isolated from the actual steps used to create the complex object and, therefore, the construction process can be reused to create different complex objects from the same set of simple objects. This tends to reduce the size of classes by factoring out methods that are responsible for constructing complex objects into a single class (called a Director) that is responsible for knowing how to build complex objects. In addition, Builder can be used when you want to create objects in a step-wise manner depending on parameters you acquire along the way.
The class diagram shown in the next figure shows the client instantiates the Builder and Director classes. The Builder represents the complex object in terms of simpler objects and primitive types. The client then passes the Builder object, as a parameter, to the Director's constructor, which is responsible for calling the appropriate Builder methods. An abstract AbstractBuilder class is created to provide the Director with a uniform interface for all concrete Builder classes. Thus, you can add new types of complex objects by defining only the structure (Builder) and reuse the logic for the actual construction process (Director). Only the client needs to know about the new types. The Director simply needs to know which Builder methods to call.