Learning the Adapter Pattern

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).

    Figure 7-1. The objects we have … should all look just like “shapes.”

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).

Figure 7-2. Points, Lines, and Squares are types of Shape.[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.

Figure 7-3. Points, Lines, and Squares showing methods.


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

Figure 7-4. Jill's XXCircle class.


  • 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).

Figure 7-5. The Adapter pattern: Circle “wraps” XXCircle.


  • 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.

Example 7-1. Java Code Fragments: Implementing the Adapter Pattern
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.

The Adapter Pattern: Key Features

Intent Match an existing object beyond your control to a particular interface.
Problem A system has the right data and behavior but the wrong interface. Typically used when you have to make something a derivative of an abstract class we are defining or already have.
Solution The Adapter provides a wrapper with the desired interface.
Participants and Collaborators The Adapter adapts the interface of an Adaptee to match that of the Adapter's Target (the class it derives from). This allows the Client to use the Adaptee as if it were a type of Target.
Consequences The Adapter pattern allows for preexisting objects to fit into new class structures without being limited by their interfaces.
Implementation Contain the existing class in another class. Have the containing class match the required interface and call the methods of the contained class.
GoF Reference Pages 139–150.

Figure 7-6. Standard, simplified view of the Adapter pattern.



..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.138.114.132