Chapter 3. Structural Patterns
Total Page:16
File Type:pdf, Size:1020Kb
Chapter 3. Structural Patterns Introduction to Structural Patterns Structural patterns describe effective ways both to partition and to combine the elements of an application. The ways structural patterns affect applications varies widely: for instance, the Adapter pattern can let two incompatible systems communicate, while Facade lets you present a simplified interface to a user without removing all the options available in the system. Adapter – To act as an intermediary between two classes, converting the interface of one class so that it can be used with the other. Bridge – To divide a complex component into two separate but related inheritance hierarchies: the functional abstraction and the internal implementation. This makes it easier to change either aspect of the component. Composite – To develop a flexible way to create hierarchical tree structures of arbitrary complexity, while enabling every element in the structure to operate with a uniform interface. Decorator – To provide a way to flexibly add or remove component functionality without changing its external appearance or function. Facade – To provide a simplified interface to a group of subsystems or a complex subsystem. Flyweight – To reduce the number of very low-level, detailed objects within a system by sharing objects. Half-Object Plus Protocol (HOPP) – To provide a single entity that lives in two or more address spaces. Proxy – To provide a representative of another object, for reasons such as access, speed, or security. 97 Adapter Also known as Wrapper Pattern Properties Type: Structural, Object Level: Component Purpose To act as an intermediary between two classes, converting the interface of one class so that it can be used with the other. Introduction One of the frequently cited advantages of object-oriented programming is that it enables code reuse. Since data and behavior are centralized in a class, you can (at least in principle) move the class from one project to another and reuse the functionality with very little effort. Unfortunately, we developers are somewhat limited in our ability to predict the future. Since we cannot know in advance what the coding requirements will be for a project in the future, we cannot always know how to design a class for optimum reusability. Imagine that, in order to speed up the development of your Personal Information Manager application, you decide to cooperate with one of your foreign friends. He has been working on a similar project and he can provide you with a commercial implementation of an address system. But when you receive the files, the interface doesn't match the interfaces you have been using. To make matters worse, the code is not in English, but in your friend’s native language. You see yourself faced with two equally unattractive solutions. Your first option is to rewrite the new component so that it implements all the required interfaces. Rewriting the new component is a bad idea because you will have to do the same rewrite every time you receive the newest version from your friend. The second option is to rewrite your own application and start using the new (foreign) interfaces. Here the downside is that you have to go through your whole code to change every occurrence of the old interfaces, and your code becomes harder to understand because you don't speak your friend’s native language. What might be meaningful names to your friend don't mean anything to you. What you need here is a translator—a component that translates the calls to one interface into calls on another interface. This is what the Adapter pattern does. It behaves similarly to a power adapter, converting one type into another otherwise-incompatible type. By using the Adapter pattern your application can keep on using your interfaces while allowing use of the new components. And when a new version arrives, the only thing you have to change is the Adapter. Applicability Use Adapter when: You want to use an object in an environment that expects an interface that is different from the object’s interface. Interface translation among multiple sources must occur. An object should act as an intermediary for one of a group of classes, and it is not possible to know which class will be used until runtime. 98 Description Sometimes you’d like to use a class in a new framework without recoding it to match the new environment. In such cases, you can design an Adaptee class to act as a translator. This class receives calls from the environment and modifies the calls to be compatible with the Adaptee class. Typical environments where an Adapter can be useful include applications that support plug-in behavior like graphics, text, or media editors. Web browsers are another environment where the Adapter can be useful. Applications involving internationalization (language conversion, for example) might benefit from Adapters, as well as those that use components (such as JavaBeans™) that are added on the fly. A real-world example of the Adapter is a foreign language phrase book. The phrase book translates common expressions (messages) from one language to another, enabling two otherwise incompatible individuals to communicate with each other. This assumes, of course, that the phrase book translates the messages properly based on the context: “I need to buy some matches,” for example, might not map correctly to “My hovercraft is full of eels” in all circumstances. Implementation The Adapter class diagram interface is shown in Figure 3.1. Figure 3.1. Adapter class diagram interface Implementing the Adapter pattern requires the following: Framework – The Framework uses the Adapter. It either constructs the ConcreteAdapter or it gets set somewhere. Adapter – The interface that defines the methods the Framework uses. ConcreteAdapter – An implementation of the Adapter interface. It keeps a reference to the Adaptee and translates the method calls from the Framework into method calls on the Adaptee. This translation also possibly involves wrapping or modifying parameters and return types. Adaptee – The interface that defines the methods of the type that will be adapted. This interface allows the specific Adaptee to be dynamically loaded at runtime. ConcreteAdaptee – An implementation of the Adaptee interface. The class that needs to be adapted so that the Framework can use this class. For more complex communication, it can be useful to establish an action map to better understand how to manage the communication. An action map is a table showing how the Adapter maps methods and call parameters between the caller and adaptee. Table 3-1 shows the mapping for a single method. Table 3-1. Example action map Framework Adapter Action Adaptee method1 None method2 argument1 None argument1 argument2 wrapper argument2 create argument3 The Unified Modeling Language (UML) can effectively represent this; typically, the sequence diagram is a useful tool for action mapping. The Adapter sequence diagram for action mapping is shown in Figure 3.2. 99 Figure 3.2. Sequence diagram for action mapping Benefits and Drawbacks The Adapter offers greatly improved reuse, allowing two or more objects to interact that would otherwise be incompatible. However, some planning and forethought are required in order to develop a framework flexible enough to be conveniently adaptable. This problem has two aspects: functional call structure and parameter translation. If there is a functional mismatch between the call framework and the Adaptee, the Adapter needs to manage the call requirements of the Adaptee, invoking any required setup methods before the framework’s call can be satisfied. Another challenge for the Adapter is the transfer of parameters, since passed parameters are not always compatible between the framework and the Adaptee. In these cases, the Adapter usually either creates appropriate objects when there is no direct equivalent between the two environments, or wraps an object to make it usable by the Adaptee. The most generic of environments for the Adapter are typically built around the Command pattern, using some form of messaging or introspection/reflection. In its most generic form, the Command pattern might eliminate the need for an Adapter. (See “ Command ” on page 51.) Pattern Variants Adapters are, by their very nature, dynamic and it’s rare to see two that are exactly alike. Nevertheless, there are some common variations. Three of these common variations are listed here: Multi-Adaptee Adapters – Depending on the system design, it can be advantageous to make an Adapter part of the calling framework. Such an Adapter often acts as an intermediary between the system and multiple Adaptees. Non-Interface-based Adapters – Use of the interface in the Java programming language makes it possible to develop even more flexible Adapters. But it may not always be possible to use interfaces. For instance, it’s not possible when you're given complete components that do not implement any interface. In those situations you will see the Adapter pattern used without using interfaces. It goes without saying that these implementations are less flexible. An interface layer between the caller and Adapter, and another between the Adapter and Adaptee – An interface layer between caller and Adapter allows new Adapters to be more easily added to the system during runtime. An interface between Adapter and Adaptee allows the Adaptees to be dynamically loaded during runtime. In combination, these interface layers make it possible to develop a truly pluggable Adapter design, where the Adaptees can be changed as needed in a running system. Related Patterns Related patterns include the following: 100 Bridge (page 150) – Although the Adapter and the Bridge pattern are very similar, their intent is different. The Bridge pattern separates the abstraction and the implementation of a component and allows each to be changed independently. The Adapter pattern allows using an otherwise incompatible existing object. Decorator (page 166) – The Adapter pattern is intended to change the interface of an object, but keep the same functionality.