Find out more about the Adapter and Decorator patterns in Java -- both involve objects that are wrappers for other objects.
Part six of a multi-part series
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.
Suppose 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:
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:
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:
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:
Passing a reference to the client object as a parameter to the adapter object's constructor or one of its methods 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:
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:
As mentioned at the beginning of this article, the decorator pattern is structurally similar to the Adapter pattern but differs greatly in the intent.
Suppose you are responsible for maintaining software of a security system for controlling physical access to a building. Its basic architecture is that a card reader or other data-entry device captures some identifying information and passes that information to an object that controls a door. If the object that controls the door is satisfied with the information, it unlocks the door. Here is a collaboration diagram showing that:
Basic Physical Access Control
Suppose you need to integrate this access control mechanism with a surveillance system. A surveillance system typically has more cameras connected to it than it has TV monitors. Most of the TV monitors will cycle through the images of different cameras. They show a picture from each camera for a few seconds and then move on to the next camera for which the monitor is responsible. There are rules about how the surveillance system is supposed to be set up to ensure its effectiveness. For this discussion, the relevant rules are:
- At least one camera covers each doorway connected to the access control system.
- Each monitor is responsible for not more than one camera that covers an access-controlled doorway. The reason is that if there are multiple cameras viewing a doorway, then the failure of a single monitor should not prevent the images from all of the cameras on that doorway being seen.
The specific integration requirement is that when an object that controls a door receives a request for the door to open, the monitors responsible for the cameras pointed at the doorway display that doorway. Your first thought about satisfying this requirement is that you will enhance a class or write some subclasses. Then you discover the relationships shown in this class diagram:
Security System Classes
There are three different kinds of doors installed and two different kinds of surveillance monitors in use. You could resolve the situation by writing two subclasses of each of the door controller classes, but you would rather not have to write six classes. Instead, you use the Decorator pattern, which solves the problem by delegation rather than inheritance.
What you do is write two new classes called DoorControllerA and DoorControllerB. These classes both implement the DoorControllerIF interface:
Door Controller Classes
The new class, AbstractDoorControllerWrapper, is an abstract class that implements all of the methods of the DoorController interface with implementations that simply call the corresponding method of another object that implements the DoorController interface. The DoorControllerA and DoorControllerB, are concrete wrapper classes. They extend the behavior of the requestOpen implementation that they inherit to also ask a surveillance monitor to display its view of that doorway. Here is a collaboration diagram showing this:
Door Surveillance Collaboration
This approach allows doorways viewed by multiple cameras to be handled by simply putting multiple wrappers in front of the DoorControllerIF object.
The following class diagram shows the general structure of the Decorator pattern:
Below are descriptions of the roles that classes play in the Decorator pattern:
- An interface in this roll is implemented by all service objects that may potentially be extended through the Decorator pattern. Classes whose instances can be used to dynamically extend classes that implement the AbstractServiceIF interface must also implement the AbstractServiceIF interface.
- Classes in this role provide the basic functionality that is extended by the Decorator pattern. The Decorator pattern extends classes in this role by using objects that delegate to them.
- The abstract class in this roll is the common superclass for wrapper classes. Instances of this class take responsibility for maintaining a reference to the AbstractServiceIF object that ConcreteWrapper objects delegate to.
- This class also normally implements all methods declared by the AbstractServiceIF interface so they simply call the like-named method of the AbstractServiceIF object that the wrapper object delegates to. This default implementation provides exactly the behavior needed for methods whose behavior is not being extended.
- ConcreteWrapperA, ConcreteWrapperB, ...
- These concrete wrapper classes extend the behavior of the methods they inherit from the AbstractWrapper class in whatever way is needed.
We conclude this discussion of the Decorator pattern with a code example that implements some of the door controller classes shown in diagrams at the beginning of this discussion.
Here is an implementation of the DoorControllerIF interface:
Here is the AbstractDoorControllerWrapper class that provides default implementations to its subclasses for the methods declared by the DoorControllerIF interface:
Finally, here is a subclass of the AbstractDoorControllerWrapper class that extends the default behavior by asking a monitor to display the image from a named camera:
Both the Adapter and Decorator patterns involve objects that are wrappers for other objects. The basic intent of the Adapter pattern is to adapt an object to an interface that we do not implement. The basic intent of the Decorator pattern is to extend the behavior of objects. This difference in intent is sometimes the clearest way to distinguish between the two.
About the Author
Mark Grand is the author of a series of books titled "Patterns in Java." He is a consultant who specializes in object-oriented design and Java. He is currently working on a framework to create integrated enterprise applications.