The easiest way to understand the intent of the Adapter pattern is to look at an example of where it is useful. Let's say I have been given the following requirements:
Create classes for points, lines, and squares that have the behavior “display.”
The client objects should not have to know whether they actually have a point, a line, or a square. They just want to know that they have one of these shapes.
In other words, I want to encompass these specific shapes in a higher-level concept that I will call a “displayable shape.”
Now, as I work through this simple example, try to imagine other situations that you have run into that are similar, such as
You have wanted to use a subroutine or a method that someone else has written because it performs some function that you need.
You cannot incorporate the routine directly into your program.
The interface or the way of calling the code is not exactly equivalent to the way that its related objects need to use it.
In other words, although the system will have points, lines, and squares, I want the client objects to think I have only shapes.
This allows the client objects to deal with all these objects in the same way—freed from having to pay attention to their differences.
It also enables me to add different kinds of shapes in the future without having to change the clients (see Figure 7-1).
I will make use of polymorphism; that is, I will have different objects in my system, but I want the clients of these objects to interact with them in a common way.
In this case, the client object will simply tell a point, line, or square to do something such as display itself or undisplay itself. Each point, line, and square is then responsible for knowing the way to carry out the behavior that is appropriate to its type.
To accomplish this, I will create a Shape class and then derive from it the classes that represent points, lines, and squares (see Figure 7-2).
[2] This and all other class diagrams in this book use the Unified Modeling Language (UML) notation. See Chapter 2, “The UML—The Unified Modeling Language,” for a description of UML notation.
First, I must specify the particular behavior that Shapes are going to provide. To accomplish this, I define an interface in the Shape class for the behavior and then implement the behavior appropriately in each of the derived classes.
The behaviors that a Shape needs to have are:
Set a Shape's location.
Get a Shape's location.
Display a Shape.
Fill a Shape.
Set the color of a Shape.
Undisplay a Shape.
I show these in Figure 7-3.
Suppose I am now asked to implement a circle, a new kind of Shape (remember, requirements always change!). To do this, I will want to create a new class—Circle—that implements the shape “circle” and derive it from the Shape class so that I can still get polymorphic behavior.
Now, I am faced with the task of having to write the display, fill and undisplay methods for Circle. That could be a pain.
Fortunately, as I scout around for an alternative (as a good coder always should), I discover that Jill down the hall has already written a class she called XXCircle that deals with circles already (see Figure 7-4). Unfortunately, she didn't ask me what she should name the methods (and I did not ask her!). She named the methods
displayIt
fillIt
undisplayIt
I cannot use XXCircle directly because I want to preserve polymorphic behavior with Shape. There are two reasons for this:
I have different names and parameter lists— The method names and parameter lists are different from Shape's method names and parameter lists.
I cannot derive it— Not only must the names be the same, but the class must be derived from Shape as well.
It is unlikely that Jill will be willing to let me change the names of her methods or derive XXCircle from Shape. To do so would require her to modify all of the other objects that are currently using XXCircle. Plus, I would still be concerned about creating unanticipated side effects when I modify someone else's code.
I have what I want almost within reach, but I cannot use it and I do not want to rewrite it. What can I do?
I can make a new class that does derive from Shape and therefore implements Shape's interface but avoids rewriting the circle implementation in XXCircle (see Figure 7-5).
Class Circle derives from Shape.
Circle contains XXCircle.
Circle passes requests made to the Circle object on through to the XXCircle object.
The diamond at the end of the line between Circle and XXCircle in Figure 7-5 indicates that Circle contains an XXCircle. When a Circle object is instantiated, it must instantiate a corresponding XXCircle object. Anything the Circle object is told to do will get passed on to the XXCircle object. If this is done consistently, and if the XXCircle object has the complete functionality the Circle object needs (I will discuss shortly what happens if this is not the case), the Circle object will be able to manifest its behavior by letting the XXCircle object do the job.
An example of wrapping is shown in Example 7-1.
class Circle extends Shape { ... private XXCircle pxc; ... public Circle () { pxc= new XXCircle(); } void public display() { pxc.displayIt(); } } |
Using the Adapter pattern allowed me to continue using polymorphism with Shape. In other words, the client objects of Shape do not know what types of shapes are actually present. This is also an example of our new thinking of encapsulation as well—the class Shape encapsulates the specific shapes present. The Adapter pattern is most commonly used to allow for polymorphism. As we shall see in later chapters, it is often used to allow for polymorphism required by other design patterns.