Adapter
This pattern was previously described in [GoF95].
Synopsis
An adapter class implements an interface known to its clients and provides access to an instance of a class not known to its clients. An adapter object provides the functionality promised by an interface without having to assume what class is used to implement that interface.
Context
Suppose that you are writing a method that copies an array of objects. The method is supposed to filter out objects that do not meet certain criteria, so that the copied array may not contain all of the elements in the original array. To promote reuse, you want the method to be independent of the filtering criteria being used. You can do this by defining an interface that declares a method the array copier can call to find out whether it should include a given object in the new array, as shown in Figure 7.1
Figure 7.1: Simple copy filter.1
In Figure 7.1, an ArrayCopier class delegates to the CopyFilterIF interface the decision to copy an element from the old array to the new array. If the isCopyable method returns true for an object, then the object is copied to the new array.
This solution solves the immediate problem of allowing the copy criteria used by ArrayCopier objects to be encapsulated in a separate object without having to be concerned about what the object’s class is. This solution presents another problem. Sometimes the logic needed for filtering is in a method of the objects being filtered. If these objects do not implement the CopyFilterIF interface, then the ArrayCopier object cannot directly ask these objects whether they should be copied. However, the ArrayCopier object can indirectly ask the filtered objects whether they should be copied, even if they do not implement the CopyFilterIF interface.
Suppose a class called Document has a method called isValid that returns a boolean result. You want an ArrayCopier object to use the result of isValid to filter a copy operation. Because Document does not implement the CopyFilterIF interface, an ArrayCopier object cannot directly use a Document object for filtering. Another object that does implement the CopyFilterIF interface cannot independently determine whether a Document object should be copied to a new array. It does not work because it has no way to get the necessary information without calling the Document object’s isValid method. The answer is for that object to call the Document object’s isValid method, resulting in the solution shown in Figure 7.2.
Figure 7.2: Copy filter adapter.
In this solution, the ArrayCopier object, as always, calls the isCopyable method of an object that implements the CopyFilterIF interface. In this case, the object is an instance of the DocumentCopyFilterAdapter class. The DocumentCopyFilterAdapter class implements the isCopyable method by delegating the call to the Document object’s isValid method.
Forces
| You want to use a class that calls a method through an interface, but you want to use it with a class that does not implement the interface. Modifying the class to implement the interface is not an option either because |
| You do not have the source code for the class. |
| The class is a general-purpose class and it would be inappropriate for it to implement an interface for a specialized purpose. |
| You want to dynamically determine which of another object’s methods an object calls without the object having knowledge of the other object’s class. |
Solution
Suppose you have a class that calls a method through an interface. You want an instance of this class to call a method of an object that does not implement the interface. You can arrange for the instance to make the call through an adapter object that implements the interface by delegating the calls to a method of the object that doesn’t implement the interface. The diagram in Figure 7.3 shows how this works.
Here are the roles that the classes and interface play in Figure 7.3:
Figure 7.3: Adapter.
Client. A class in this role calls a method of another class through an interface to avoid assuming the actual class that implements the method.
TargetIF. An interface in this role declares a method that the client class calls.
Adapter. A class in this role implements the TargetIF interface. It implements the method that the Client calls by delegating the call to a method of the Adaptee class, which does not implement the TargetIF interface.
Adaptee. A class in this role does not implement the TargetIF method but has a method that the Client class wants to call.
It is possible for an Adapter class to do more than simply delegate a method call. It may perform some transformation on the arguments. It may provide additional logic to hide differences between the intended semantics of the interface’s method and the actual semantics of the Adaptee class’s method. There is no limit to how complex an adapter class can be. So long as the essential purpose of the class is as an intermediary for method calls to one other object, you can consider it to be an adapter class.
Implementation
Implementation of the adapter class is rather straightforward. However, an issue you should consider when implementing the Adapter pattern is how the Adapter objects will know which instance of the Adaptee class to call. There are two approaches.
| Pass a reference to the Adaptee object as a parameter to the adapter object’s constructor or one of its methods. This allows the Adapter object to be used with any instance or possibly multiple instances of the Adaptee class. This approach is illustrated in the following example: |
class CustomerBillToAdapter implements AddressIF { | |
| Make the adapter class an inner class of the Adaptee class. This simplifies the association between the adapter object and the Adaptee object by making it automatic. It also makes the association inflexible. This approach is illustrated in the following example: |
|
Consequences
| The Client and Adaptee classes remain independent of each other. |
| You can use an adapter class to determine which of an object’s methods another object calls. For example, you may have a class whose instances are GUI widgets that allow telephone numbers to be displayed and edited. This class fetches and stores telephone numbers by calling methods defined by an interface. To make use of the interface you define adapter classes. You may have an adapter class to fetch and store a fax number from instances of a class and another adapter class to fetch and store pager numbers from instances of the same class. The difference between the two adapter classes would be that they call different methods of the Adaptee class. Here is an example. Suppose there is a class named PhoneNumberEditor that is responsible for allowing a user to edit a phone number. An object is passed to the constructor of a PhoneNumberEditor that implements this interface. |
public interface PhoneNumberIF { | |
If you want to create two PhoneNumberEditor objects to edit the office and fax numbers of a person, you might write some code that looks like this: | |
PhoneNumberEditor voiceNumber; | |
Each PhoneNumberEditor object is created with a different adapter. Each adapter calls different methods of the same object. | |
| The Adapter pattern adds indirection to a program. Like any other indirection, it contributes to the difficulty involved in understanding the program. |
Java API Usage and Example
A very common way to use adapter classes with the Java API is for event handling, like this:
Button ok = new Button("OK");
ok.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent evt) {
doIt();
} // actionPerformed(ActionEvent)
} );
add(ok);
The previous example creates an instance of an anonymous class that implements the ActionListener interface. The class’s actionPerformed method is called when the Button object is pressed. This coding pattern is very common for code that handles events.
The Java API does not include any public adapter classes that are ready to use. It does include classes such as java.awt.event.WindowAdapter that are intended to be subclassed rather than used directly. The idea is that some event listener interfaces, such as WindowListener, declare multiple methods that may not all need to be implemented in many cases. The WindowListener interface declares eight methods that are called to provide notification about different kinds of window events. Often only one or two of those event types are of interest. The methods corresponding to events that are not of interest will typically be given do-nothing implementations. The WindowAdapter class implements the WindowListener interface and implements all eight of its methods with do-nothing implementations. An adapter class that subclasses the WindowAdapter class needs to implement only those methods corresponding to events that are of interest. It inherits do-nothing implementations for the rest. For example:
addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent e) {
System.exit();
} // windowClosing(WindowEvent)
} );
In the preceding example, the anonymous adapter class is a subclass of the WindowAdapter class. It only implements the windowClosing method. It inherits do-nothing implementations for the other seven methods from the WindowAdapter class.
Related Patterns
Façade. The Adapter class provides an object that acts as an intermediary for method calls between a client object and one other object not known to the client objects. The Façade pattern provides an object that acts as an intermediary for method calls between its client object and multiple objects not known to the client objects.
Iterator. The Iterator pattern is a specialized form of the Adapter pattern for sequentially accessing the contents of a collection of objects.
Proxy. The Proxy pattern, like the Adapter pattern, uses an object that is a surrogate for another object. However, a Proxy object has the same interface as the object for which it is a surrogate.
Strategy. The Strategy pattern is structurally similar to the Adapter pattern. The difference is in the intent. The Adapter pattern allows a Client object to carry out its originally intended function by calling methods of objects that implement a particular interface. The Strategy pattern provides objects that implement a particular interface for the purpose of altering or determining the behavior of a Client object.
Anonymous Adapter. The Anonymous Adapter pattern (described in Patterns in Java, Volume 2) is a coding pattern that uses anonymous Adapter objects to handle events.
No comments:
Post a Comment