Adapter design pattern in Java and Python Development 31.12.2016

Structural Design Patterns provide different ways to create a class structure, for example using inheritance and composition to create a large object from small objects. Following design patterns come under this category.

Adapter pattern lets you wrap an otherwise incompatible object in an adapter to make it compatible with another class.

Adapter pattern works as a bridge between two incompatible interfaces. Sometimes, there could be a scenario when two objects don’t fit together, as they should in-order to get the work done. This situation could arise when we are trying to integrate a legacy code with a new code, or when changing a 3rd party API in the code. This is due to incompatible interfaces of the two objects which do not fit together. In this case we can use Adapter design pattern.

As a real life example, we can think of a mobile charger as an adapter because mobile battery needs 3 volts to charge but the normal socket produces either 120V (US) or 240V (India). So the mobile charger works as an adapter between mobile charging socket and the wall socket.

Adapter design pattern is used so that two unrelated interfaces can work together. The object that joins these unrelated interfaces is called an Adapter. In the adapter pattern, a wrapper class (i.e., the adapter) is used translate requests from it to another class (i.e., the adoptee). In effect, an adapter provides particular interactions with an adoptee that are not offered directly by the adoptee.

UML diagram of generic adapter design pattern:

dp_adapter.png

Elements:

  • Target defines domains-specific interface client uses.
  • Client collaborates with objects conforming to target interface.
  • Adaptee defines existing interface that needs adapting.
  • Adapter adapts the interface of adaptee to target interface.

The Adapter pattern should be used when:

  • There is an existing class, and its interface does not match the one you need.
  • You want to create a reusable class that cooperates with unrelated or unforeseen classes, that is, classes that don’t necessarily have compatible interfaces.
  • There are several existing subclasses to be use, but it’s impractical to adapt their interface by subclassing every one. An object adapter can adapt the interface of its parent class.

Let's take a look at the interactions in a sequence diagram.

dp_adapter_seq.png

Java

So first of all we will have two classesVolt (to measure volts) and Socket (producing constant volts of 120V).

public class Volt {
    private int volts;
    public Volt(int v) { this.volts=v; }
    public int getVolts() { return volts; }
    public void setVolts(int volts) { this.volts = volts; }
}
public class Socket {
    public Volt getVolt(){ return new Volt(120); }
}

Now we want to build an adapter that can produce 3 volts, 12 volts and default 120 volts. So first of all we will create an adapter interface with these methods.

public interface SocketAdapter {
    public Volt get120Volt();
    public Volt get12Volt();
    public Volt get3Volt();
}

While implementing Adapter pattern, there are two approaches – class adapter and object adapter – however both these approaches produce same result.

  • Class Adapter. This form uses java inheritance and extends the source interface, in our case Socket class.
  • Object Adapter. This form uses java composition and adapter contains the source object.

Here is the class adapter approach implementation of our adapter.

// using inheritance for adapter pattern
public class SocketClassAdapterImpl extends Socket implements SocketAdapter {
    @Override
    public Volt get120Volt() {
        return getVolt();
    }

    @Override
    public Volt get12Volt() {
        Volt v = getVolt();
        return convertVolt(v,10);
    }

    @Override
    public Volt get3Volt() {
        Volt v = getVolt();
        return convertVolt(v,40);
    }

    private Volt convertVolt(Volt v, int i) {
        return new Volt(v.getVolts()/i);
    }
}

Here is the object adapter implementation of our adapter.

public class SocketObjectAdapterImpl implements SocketAdapter {
    // using composition for adapter pattern
    private Socket sock = new Socket();

    @Override
    public Volt get120Volt() {
        return sock.getVolt();
    }

    @Override
    public Volt get12Volt() {
        Volt v = sock.getVolt();
        return convertVolt(v,10);
    }

    @Override
    public Volt get3Volt() {
        Volt v = sock.getVolt();
        return convertVolt(v,40);
    }

    private Volt convertVolt(Volt v, int i) {
        return new Volt(v.getVolts()/i);
    }
}

Here is a test program to consume our adapter design pattern implementation.

public class AdapterPatternTest {
    public static void main(String[] args) {
        testClassAdapter();
        testObjectAdapter();
    }

    private static void testObjectAdapter() {
        SocketAdapter sockAdapter = new SocketObjectAdapterImpl();
        Volt v3 = getVolt(sockAdapter,3);
        Volt v12 = getVolt(sockAdapter,12);
        Volt v120 = getVolt(sockAdapter,120);
        System.out.println("v3 volts using Object Adapter="+v3.getVolts());
        System.out.println("v12 volts using Object Adapter="+v12.getVolts());
        System.out.println("v120 volts using Object Adapter="+v120.getVolts());
    }

    private static void testClassAdapter() {
        SocketAdapter sockAdapter = new SocketClassAdapterImpl();
        Volt v3 = getVolt(sockAdapter,3);
        Volt v12 = getVolt(sockAdapter,12);
        Volt v120 = getVolt(sockAdapter,120);
        System.out.println("v3 volts using Class Adapter="+v3.getVolts());
        System.out.println("v12 volts using Class Adapter="+v12.getVolts());
        System.out.println("v120 volts using Class Adapter="+v120.getVolts());
    }

    private static Volt getVolt(SocketAdapter sockAdapter, int i) {
        switch (i){
            case 3: return sockAdapter.get3Volt();
            case 12: return sockAdapter.get12Volt();
            case 120: return sockAdapter.get120Volt();
            default: return sockAdapter.get120Volt();
        }
    }
}

Python

class Volt:
    def __init__(self, volt):
        self.volt = volt

    def get_volt(self):
        return self.volt

    def set_volt(self):
        self.volt = volt


class Socket:
    def get_volt(self):
        return Volt(120)


class SocketClassAdapterImpl(Socket):
    def get_120volt(self):
        return self.get_volt()

    def get_12volt(self):
        v = self.get_volt()
        return self.convert_volt(v, 10)

    def get_3volt(self):
        v = self.get_volt()
        return self.convert_volt(v, 40)

    def convert_volt(self, v, i):
        return Volt(v.get_volt() / i)


def get_volt(sock_adapter, i):
    if i == 3:
        return sock_adapter.get_3volt()
    elif i == 12:
        return sock_adapter.get_12volt()
    elif i == 120:
        return sock_adapter.get_120volt()

    return sock_adapter.get_120volt()


if __name__ == '__main__':
    sock_adapter = SocketClassAdapterImpl()

    v3 = get_volt(sock_adapter, 3)
    v12 = get_volt(sock_adapter, 12)
    v120 = get_volt(sock_adapter, 120)

    print("v3 volts using Class Adapter=" + str(v3.get_volt()))
    print("v12 volts using Class Adapter=" + str(v12.get_volt()))
    print("v120 volts using Class Adapter=" + str(v120.get_volt()))