Skip to main content

Agile Software Development: Principles, Patterns,and Practices -- The Adapter Pattern

August 17, 2004

{cs.r.title}










Contents
Decoupling from
a Member You Can't Modify
The Adapter Pattern
The Class Form of Adapter
Adapter
Implemented with Anonymous Inner Class
Adapt a Sender's Protocol

In my last column we talked about the
Abstract Server
pattern in the context of the Button and Light example. At the end of that column, I promised that we'd study the pattern that we might use if we could not modify Light.

Decoupling
from a Member You Can't Modify

Consider a Button class that uses a Light class as follows:


A Button class that uses a Light class.
public class Button {
  private Light light;

  public Button(Light light) {
    this.light = light;
  }

  public void press() {
    light.turnOn();
  }
}

How can be break the dependency between Button and Light, and thus conform to the SOLID principles of OOD, if we can't modify the Light class?

Why can't we modify the Light class? Perhaps we don't own the source code. Or perhaps there are so many users of Light that forcing them all to rebuild and redeploy would be very expensive. Whatever the reason might be, we have decided that Light should not be modified.

The Adapter Pattern

Remember that a design pattern is a named solution to a problem in a context. In our case, the problem is decoupling Button from Light. The context is that Light cannot be modified. The most common solution for this problem/context pair is the Adapter pattern.

As shown in the diagram below, this pattern adds an extra object to the Abstract Server solution that we discussed in the last column. This extra object, called LightAdapter, implements the Switchable interface and delegates messages received by that interface to the associated Light object.

This pattern adds an extra object to the Abstract Server solution.

The code that implements the Adapter is trivial.

public interface Switchable {
  void turnOn();
}

public class LightAdapter implements Switchable {
  private Light light;

  public LightAdapter(Light light) {
    this.light = light;
  }

  public void turnOn() {
    light.turnOn();
  }
}

This solves the problem nicely. The Button class no longer knows about the Light, and the Light has not been modified. However, there are some costs. The unit test below shows how the LightAdapter has to be wired to the Light. This extra step in binding the Button to the Light adds complexity to the application. Moreover, the LightAdapter object itself requires memory and consumes CPU cycles. These costs may be small, but they are enough to discourage speculative use of the Adapter.

public class AdapterTest extends TestCase {
  public void testButtonControlsLight() throws Exception {
    Light l = new Light();
    LightAdapter la = new LightAdapter(l);
    Button b = new Button(la);
    b.press();
    assertTrue(l.isOn());
  }
}

The Class Form of Adapter

The pattern as shown above is known as the object form of the Adapter. The class form of the Adapter addresses some of the costs, while incurring others. It is shown in the diagram and code below.

The Class Form of Adapter
public class LightClassAdapter extends Light implements Switchable {
}

Isn't it wonderful that this class has no body? And yet, it completely fulfills its role as an Adapter. The unit test below shows how easy this form of the Adapter is to use.

  public void testButtonControlsLightThroughClassAdapter() throws Exception {
    LightClassAdapter lca = new LightClassAdapter();
    Button b = new Button(lca);
    b.press();
    assertTrue(lca.isOn());
  }

Like the Abstract Server pattern, the class form of the Adapter pattern does not add a new object to the application. Moreover, there is no extra wiring, nor significant extra storage, nor any extra CPU cycles involved. It is as fast and small as the Abstract Server, and yet it breaks the dependency between Button and Light.

On the other hand, it lacks flexibility. The object form of the Adapter allows you to swap different instances of Light into and out of the LightAdapter object. The class form of the Adapter is the Light, and can never be used to hold a different instance of Light. Moreover, everyone who creates the Light must know to create a LightClassAdapter instead. Thus, the creators of Light are coupled to the solution in a way that the object form of the Adapter avoids.

One last cost of the class form of the Adapter is that it uses up the one and only slot for inheritance. I am consistently annoyed that Java does not allow true multiple inheritance. There are times when a class-form Adapter would be nice to use, and yet, the cost of using that slot is too great to pay.

Now I'll rant. Java should have multiple inheritance. It's ridiculous that a 21st-century language hobbles its users by not allowing something as simple as inheriting from multiple base classes. Yes, I know that the diamond problem leads to some nasty ambiguities; but a reasonable solution to this is to disallow diamonds, or force all diamonds to converge on Object. Multiple inheritance is useful, damnit! I might not use it every day; but when I want it I want it. OK, I'll stop ranting now.

Adapter Implemented with Anonymous Inner Class

The anonymous inner class feature of Java can be a wonderful way to create object-form Adapters. Consider the following code:

public class AdapterTest extends TestCase {
  private Light l = new Light();

  public void testAnonymousInnerClassAdapter() throws Exception {
    Switchable s = new Switchable() {
      public void turnOn() {
        l.turnOn();
      }
    };
    Button b = new Button(s);
    b.press();
    assertTrue(l.isOn());
  }
}

An anonymous inner class is used to delegate to the Light field of the AdapterTest object. This is nice, because we don't have to create a whole new class and try to come up with some hokey name like LightAdapter.

This technique is commonly used with listeners in Java. For example, if we have two JButtons on a dialog box, we can use anonymous inner adapters to create listeners for them.

public class SimpleDialog extends JFrame {
  private JButton okButton = new JButton("OK");
  private JButton cancelButton = new JButton("CANCEL");

  private void ok() {/* called when OK is pressed */}
  private void cancel() {/* called when CANCEL is pressed */}

  public SimpleDialog() throws HeadlessException {
    okButton.addActionListener(
      new ActionListener() {
        public void actionPerformed(ActionEvent e) {
          ok();
        }
      }
    );

    cancelButton.addActionListener(
      new ActionListener() {
        public void actionPerformed(ActionEvent e) {
          cancel();
        }
      }
    );
  }
}

These cute little anonymous Adapters are easy to create, and they nicely bind each JButton to the appropriate methods in the SimpleDialog class. We might show this in UML as follows:

These cute little anonymous Adapters are easy to create, and they nicely bind each JButton to the appropriate methods in the SimpleDialog class.

Adapt a Sender's Protocol

Another use of the Adapter pattern is to adapt the protocol of a sender to a receiver. For example, let's say that we have a class that looks like this:

public class ThreeWayLight {
  private int brightness = 0;
  public void lo() {brightness = 1;}
  public void medium() {brightness = 2;}
  public void high() {brightness = 3;}
  public void off() {brightness = 0;}

  public int getBrightness() {
    return brightness;
  }
}

The Button was never designed to control a three-way light. Moreover, it looks as though the ThreeWayLight class was not designed to take input from a Button. How can we adapt the Button class to the ThreeWayLight ? Consider the following unit test:

public void testThreeWayLight() throws Exception {
    ThreeWayLight twl= new ThreeWayLight();
    ThreeWayAdapter twa = new ThreeWayAdapter(twl);
    Button b = new Button(twa);
    assertEquals(0, twl.getBrightness());
    b.press();
    assertEquals(1, twl.getBrightness());
    b.press();
    assertEquals(2, twl.getBrightness());
    b.press();
    assertEquals(3, twl.getBrightness());
    b.press();
    assertEquals(0, twl.getBrightness());
  }

Clearly our intent is that the ThreeWayAdapter should ratchet the ThreeWayLight through its states every time the Button is pressed. We can easily implement this adapter as follows:

public class ThreeWayAdapter implements Switchable {
  private ThreeWayLight twl;

  public ThreeWayAdapter(ThreeWayLight twl) {
    this.twl = twl;
  }

  public void turnOn() {
    switch (twl.getBrightness()) {
      case 0:
        twl.lo();
        break;
      case 1:
        twl.medium();
        break;
      case 2:
        twl.high();
        break;
      case 3:
        twl.off();
        break;
    }
  }
}

This shows how an Adapter can be used to adjust the protocol of the sender (i.e., the Button) to the protocol of the receiver (i.e., the ThreeWayLight). The two classes were never designed to work together, and yet we can easily adapt them without having to change them.

We'll see the Adapter pattern again as we explore yet other patterns and design principles in the months to follow. Adapter is a simple, yet very useful, way to decouple classes from each other, especially when the target class should not be changed. Adapter is also a useful way to adapt the protocols of two classes together without directly affecting them.

Robert C. Martin (Uncle Bob) has been a software professional since 1970 and an international software consultant since 1990.