The definition of the Adapter design pattern according to GOF (Gang Of Four) in Design Patterns: Elements of Reusable Object-Oriented Software is as follows:

Converts the interface of class into another interface the clients expect. Adapter lets classes work together that couldn’t otherwise because of incompatible interfaces. (p.139)

To illustrate it we are going to start with a code base and then try to integrate it to an “external” code base using the Adapter design pattern.

The adapter pattern comes in two flavors:

  1. Object Adapter: Uses interfaces to benefit from polymorphism; this flavor is used if your language of implementation is Java.
  2. Class Adapter: Can be used in languages that support multiple inheritance, this technique can be used if your language of implementation is C++.

In this example we are going to look at the Object adapter implementation.

Note 1: I interpret that “clients” in the definition by Gang of Four above means the calling code. In some instances “client” might also refer to the starting code base.

Note 2: “Interface” in the definition by Gang of Four above means properties and methods of a class. But it can also be a proper Java interface.

Requirements

Conceptually both systems that you are trying to integrate must have a coherent conceptual correspondence.

Implementation

A real life example of adapters I faced is when I have purchased Europe based electronic devices that I later had to use in the US. In order to power them up, or charge them up depending on the device I had to get a Targus Travel AC Power Adapter. Recall that the adapter worked in that case because even though the interfaces were different, the problem domain was the same.

Let’s say that we have a program that works with US based heating devices; its reputation is so good that it has become a market leader, so now we want to go global and sell it in Europe and other parts of the world.

Starting Code Base

Because we are good OO coders, we code to interfaces. Here is our interface for US heating devices:

package adapter;

public interface USHeatingDevice {

     public int voltage();
     public int frequency();

}

Here is an implementation of our interface:

package adapter;

public class TowerHeater implements USHeatingDevice {

    public int voltage() {
        return 120;
    }

    public int frequency() {
        return 60;
    }
}

Here is what I interpret as the client per Note 1 above:

package adapter;

public class Starter {

       public static void main(String[] args){

          USHeatingDevice usHeatingDevice = new TowerHeater();
          System.out.println(usHeatingDevice.voltage() + " V");
          System.out.println(usHeatingDevice.frequency() + " Hz");

       }
}

Running the program produces the following output:
120 V
60 Hz

Now we want/need to expand our program to work with foreign heating devices manufactured in Europe, South America, Asia etc. The problem is that their voltage and frequency are not the same, so our program will not work. Imagine that there is a vendor that has already developed an interface that can be hooked into our program so that it can work internationally. The code is illustrated below.

Vendor Code Base

Because the vendor also uses good OO techniques, here is their interface for non US heating devices:

package adapter.vendor;

public interface NonUSHeatingDevice {

    public int tension();

    public int hertz();
}

Here is their European implementation:

package adapter.vendor;

public class EUTowerHeater implements NonUSHeatingDevice {

    public int tension() {
        return 230;
    }

    public int hertz() {
        return 50;
    }
}

Notice two things here:

  1. The voltage and frequency have different values.
  2. The interface methods developed by the vendor have different names.

Problem

In order to use our program using the non US implementation we would have to find all our places where we make calls to voltage() and frequency() and put some sort of if/else logic to support calling tension() and hertz() methods when using the program in Europe.

Solution

Well that could be quite tedious, couldn’t it? Here is where the design pattern comes into place; the goal is to write and adapter that will implement our current existing interface (USHeatingDevice) but in reality will be executing an implementation of (NonUSHeatingDevice) interface. This technique will allow us to keep voltage() and frequency() calls throughout or code  and we will only have to make a small change in the calling code, the client.

Note: Some authors state that the adapter pattern allows you to not do any code changes in the “client” once you have created the adapter class see (p.237) of Head First Design Patterns. I completely disagree with that and you will see it in the following snippets.

First let’s create the adpater:

package adapter;

import adapter.vendor.NonUSHeatingDevice;

public class HeaterAdapter implements USHeatingDevice {      

     private NonUSHeatingDevice europeanHeatingDevice;     

     public HeaterAdapter(NonUSHeatingDevice europeanHeatingDevice){

         this.europeanHeatingDevice = europeanHeatingDevice;
     }

     public int voltage() {

         return europeanHeatingDevice.tension();
     }  

     public int frequency() {  

         return europeanHeatingDevice.hertz();
     }
}

We see as follows:

  1. The Adapter class implements our known interface (USHeatingDevice), the one we are used to work with (because it will be the one that the client will continue to work with).
  2. The Adapter class has a data member that is of type that we are adapting to (NonUSHeatingDevice).
  3. The implemented methods call the matching methods in the non US heating device interface.

As you can see we are dressing up the HeaterAdapter by making it look like a US heater device, but really behaving like a non US heater device.

Now let’s make the changes to the client to work with the HeaterAdapter that we have just created:

package adapter;

import adapter.vendor.EUTowerHeater;
import adapter.vendor.NonUSHeatingDevice;

public class Starter {

       public static void main(String[] args){

          USHeatingDevice usHeatingDevice = null;
          NonUSHeatingDevice euTowerHeater = null;

          if(args[0].equals("US")){

            usHeatingDevice = new TowerHeater();

          }else if(args[0].equals("EU")){

            euTowerHeater = new EUTowerHeater();
            usHeatingDevice = new HeaterAdapter(euTowerHeater);

          }

          System.out.println(usHeatingDevice.voltage() + " V");
          System.out.println(usHeatingDevice.frequency() + " Hz");

       }
}

Notice the changes in the client from the original Starter.java; Pay attention to lines 19 and 20, we go through the adapter to instantiate a USHeatingDevice, which will behave as a non US heating device. As we can see on lines 24 and 25 The rest of the program is still working with the same interface as it was using before the vendor code was integrated into our application.

If we were to run this program with the EU command line argument we would get the result as follows:
230 V
50 Hz

Adapter Pattern as an abstraction layer

Another advantage of this pattern is that many implementations of the adaptee (vendor interface) could be added by the vendor and we would not have to touch a line of code in our HeaterAdapter.java thanks to Polymorphism.

Let’s say that the vendor add implementations of their non US heating device interface for countries such as Jamaica, Libya, and Colombia which have different electric power specification.

package adapter.vendor;

public class JamaicaTowerHeater implements NonUSHeatingDevice {

     public int tension() {
         return 110;
     }

     public int hertz() {
         return 50;
     }
}

The colombian implementation….

package adapter.vendor;

public class ColombianTowerHeater implements NonUSHeatingDevice {

    public int tension() {
        return 110;
    }

    public int hertz() {
        return 60;
    }
}

…and the Libya implementation

package adapter.vendor;

public class LibyaTowerHeater implements NonUSHeatingDevice {

    public int tension() {
        return 127;
    }

    public int hertz() {
        return 50;
    }
}

Let’s see the changes we have to make to the client, without touching the adapter at all.

package adapter;

import adapter.vendor.*;

public class Starter {

     public static void main(String[] args) {

        if (args.length > 0) {

            USHeatingDevice usHeatingDevice = null;

           if (args[0].equals("US")) {

                usHeatingDevice = new TowerHeater();

           } else if (args[0].equals("EU")) {

                NonUSHeatingDevice euTowerHeater = new EUTowerHeater();
                usHeatingDevice = new HeaterAdapter(euTowerHeater);

           } else if (args[0].equals("JM")) {

                NonUSHeatingDevice jamaicaTowerHeater = new JamaicaTowerHeater();
                usHeatingDevice = new HeaterAdapter(jamaicaTowerHeater);

           } else if (args[0].equals("CO")) {

                NonUSHeatingDevice colombianTowerHeater = new ColombianTowerHeater();
                usHeatingDevice = new HeaterAdapter(colombianTowerHeater);

           } else if (args[0].equals("LY")) {

                NonUSHeatingDevice libyaTowerHeater = new LibyaTowerHeater();
                usHeatingDevice = new HeaterAdapter(libyaTowerHeater);

           } else {
               System.out.println("You must pass an apppropriate region parameter");
           }

           System.out.println(usHeatingDevice.voltage() + " V");
           System.out.println(usHeatingDevice.frequency() + " Hz");

        }

     }
}

As you can see supporting new functionality is just as easy as adding the highlighted lines above.

The UML diagram below summarizes our example:

Adapter Pattern

Adding flexibility to the Adapter Pattern

Thanks to Lariza Saenz a colleague from JavaHispano user group for pointing this out in the post comments. Let’s suppose that you are not interested in implementing all the methods of the interface. You could do it as follows:

1. Implement the method in the HeaterAdapter class and throw an UnsupportedOperationException.
2. or create an abstract class AbstractHeaterAdapter.

Let’s illustrate step #2. The AbstractHeaterAdapter class would look as follows:

package adapter;

public abstract class AbstractHeaterAdapter implements USHeatingDevice {

	@Override
	public int voltage() {
		// TODO Auto-generated method stub
		return 0;
	}

	@Override
	public int frequency() {
		// TODO Auto-generated method stub
		return 0;
	}

}

The implementation class would looks as follows:

package adapter;

public class Starter2 {

	public static void main(String[] args) {

		USHeatingDevice usHeatingDevice = new AbstractHeaterAdapter(){

			public int voltage() {

				return 125;
			}
		};

		System.out.println(usHeatingDevice.voltage());

    }
}

A real world example of this technique can be found in the java.awt.event package. Every listener interface (e.g MouseListener) has its correspondent adapter (MouseAdapter). The MouseAdapter is an abstract class that implements the MouseListener interface. So when you want to attached a mouseClicked event to a button you an either do it through a MouseListener or a MouseAdapter. If you do it trhough the MouseListener you will have to implement all the methods defined in the interface (mouseClicked, mousePressed, mouseReleased, mouseEntered). But what happens if you are only interested in one of the methods? Then you would use a MouseAdaper and provide the implementation for the method(s) that you are interested only similar to the example in Starter2.java above.

Bonus: Improving the client

Bladimir Rondon another colleague from the JavaHispano users group has suggested to use a ResourceBundle and reflection to clean up the if/else ugly code in Starter.java . The main advantage of using a resource bundle is as follows:

  1. You will be able to add implementations and not have to do any client code changes (except for the first time when you add support for the NonUSHeatingDevice).
  2. You will only have to make entries in the implementation.properties file for each new implementation.

The implementation.properties file will look as follows:

US=adapter.TowerHeater
EU=adapter.vendor.EUTowerHeater
CO=adapter.vendor.ColombianTowerHeater
JM=adapter.vendor.JamaicaTowerHeater
LY=adapter.vendor.LibyaTowerHeater

The Starter.java will look as follows:

package adapter;

import java.util.ResourceBundle;

import adapter.vendor.NonUSHeatingDevice;

public class Starter { 

	public static void main(String[] args) { 

		if (args.length > 0) { 

			ResourceBundle resBun = ResourceBundle.getBundle("implementations");

			USHeatingDevice heatingDevice = null;

			//If creation of a USHeatingDevice fails, then try to create a NonUSHeatingDevice
			try {
				heatingDevice = (USHeatingDevice) Class.forName(resBun.getString(args[0])).newInstance(); 

			} catch (Exception e){

				NonUSHeatingDevice foreignDevice = null;
				try {
					foreignDevice = (NonUSHeatingDevice) Class.forName(resBun.getString(args[0])).newInstance();
					heatingDevice = new HeaterAdapter(foreignDevice);
				} catch (Exception z){
					System.out.println("Check your properties file");
				}

			} 

			System.out.println(heatingDevice.voltage() + " V");
			System.out.println(heatingDevice.frequency() + " Hz"); 

		} else {
			System.out.println("You must pass an apppropriate region parameter");
		}
	}
}

Now you could put a Facade Pattern to hide all the ugly try/catch out the main class into an static method in another class to improve this further.

Conclusions

The adapter pattern can be used in the situations as follows:

  1. To match methods of a new interface that has similar behavior but different method names.
  2. To allow one and only one adapter to work with many different implementations of the Adaptee.
  3. To provide custom behavior for a desired method(s) of the interface without having to implement all methods of the contract.

It is also good for:

  1. Illustrating the power of Polimorphysm.
  2. Promoting the good OO technique of coding to interfaces.