Pattern Summaries 6: Adapter and Decorator
Part six of a multi-part series
Previous article: Pattern Summaries: Prototype
This is part of an ongoing series of articles in which I will summarize patterns from my "Patterns in Java" series of books.
The essence of a pattern is a reusable solution for a recurring problem. A complete pattern will also provide reasons to use and not use the solution, the consequences of using the solution and suggestions on how to implement the solution. The summaries in these articles will just describe the essential problem and its solution.
This is the first of a group of patterns that are related to the low level structure of the classes in an application. This article discusses patterns called Adapter and Decorator, which come from Volume 1 of Patterns in Java. These patterns are in the same article because they are structurally similar.
AdapterSuppose you are writing a method that copies an array of objects, filtering out objects that do not meet certain criteria. To promote reuse, you would like to make the method 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 if it should include a given object in the new array:
Figure 1. Simple Copy Filter
In the above design, 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 that 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. The problem is that the filtering logic is in a different object than the objects being filtered. Sometimes the logic needed for the filtering is in a method of the objects being filtered. If those objects do not implement the CopyFilterIF interface, then there is no way for the ArrayCopier object to directly ask those objects if they should be copied. However, it is possible for the ArrayCopier object to indirectly ask the filtered objects if 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 if 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 this solution:
Figure 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 calling the Document object's isValid method.
Now we will look at the problem and its solution in more general terms.
Suppose that you have a class that calls a method through an interface. You want an instance of that 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 with a method that calls a method of the object that doesn't implement the interface. Here is a collaboration diagram showing how this works:
Figure 3. Adapter
Here are the roles the that the classes and interface play in this general solution:
- This is a class that calls a method of another class through an interface to avoid assuming that the whose method it calls belongs to a specific class.
- This interface declares the method that the client class calls.
- This class implements the TargetIF interface. It implements the method that the client calls by having it call a method of the Adaptee class, which does not implement the TargetIF interface.
- This class does not implement the TargetIF method but has a method the Client class wants to call.
It is possible for an Adapter class to do more than simply delegate the 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' 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 considered it to be an Adapter class.
Implementation of the adapter class is rather straightforward. However, an issue that you should consider when implementing the Adapter pattern is how the Adapter objects will know what instance of the Adaptee class to call. There are two approaches: