Wednesday, July 6, 2022

Structural Design Patterns # Adapter

Adapter is a structural design pattern that allows objects with incompatible interfaces to collaborate.


Imagine that you’re creating a stock market monitoring app. The app downloads the stock data from multiple sources in XML format and then displays nice-looking charts and diagrams for the user.

At some point, you decide to improve the app by integrating a smart 3rd-party analytics library. But there’s a catch: the analytics library only works with data in JSON format.

You could change the library to work with XML. However, this might break some existing code that relies on the library. And worse, you might not have access to the library’s source code in the first place, making this approach impossible.


You can create an adapter. This is a special object that converts the interface of one object so that another object can understand it.

An adapter wraps one of the objects to hide the complexity of conversion happening behind the scenes. The wrapped object isn’t even aware of the adapter. For example, you can wrap an object that operates in meters and kilometers with an adapter that converts all of the data to imperial units such as feet and miles.

Adapters can not only convert data into various formats but can also help objects with different interfaces collaborate. Here’s how it works:

  1. The adapter gets an interface, compatible with one of the existing objects.
  2. Using this interface, the existing object can safely call the adapter’s methods.
  3. Upon receiving a call, the adapter passes the request to the second object, but in a format and order that the second object expects.
Sometimes it’s even possible to create a two-way adapter that can convert the calls in both directions.


Pros and Cons

One of the great real-life examples of Adapter design patterns is the mobile charger.

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 Class Diagram

So first of all we will have two classes – Volt (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(); }

Two Way Adapter Pattern

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 the adapter contains the source object.

Adapter Design Pattern – Class Adapter

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); } }

Adapter Design Pattern – Object Adapter Implementation

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); } }

Notice that both the adapter implementations are almost same and they implement the SocketAdapter interface. The adapter interface can also be an abstract class.

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(); } } }

When we run above test program, we get following output.
v3 volts using Class Adapter=3 v12 volts using Class Adapter=12 v120 volts using Class Adapter=120 v3 volts using Object Adapter=3 v12 volts using Object Adapter=12 v120 volts using Object Adapter=120

Follow on LinkedIn

You may also like

Kubernetes AWS Java Coding Question
Microservices Core Java Spring Boot
Spring Framework Kafka Miscellaneous