Connect Incompatible Interfaces in Java with the Adapter Design Pattern
Learn how the Adapter Design Pattern in Java harmoniously brings together incompatible interfaces. Discover realistic applications and implementations of this useful pattern, from mobile chargers to Java classes.
A Real-Life Example
A great real-life example of the Adapter Design Pattern is a mobile charger. The mobile battery requires 3 volts to charge, but the regular socket provides either 120 volts (USA) or 240 volts (India). So, the mobile charger acts as an adapter between the mobile charging port and the wall socket.
Implementation of a Multi-Adapter
We will attempt to implement a multi-adapter using the Adapter Design Pattern in this tutorial. To do this, we will need two classes initially: Volt
(to measure volts) and Socket
(to generate a constant 120 volts).
package com.journaldev.design.adapter;
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;
}
}
package com.journaldev.design.adapter;
public class Socket {
public Volt getVolt() {
return new Volt(120);
}
}
Create an Adapter Interface
Now, let’s create an adapter that can produce 3 volts, 12 volts, and default 120 volts. For this purpose, we create an adapter interface with these methods:
package com.journaldev.design.adapter;
public interface SocketAdapter {
public Volt get120Volt();
public Volt get12Volt();
public Volt get3Volt();
}
There are two approaches to implementing the Adapter Pattern: the class adapter and the object adapter. However, both approaches achieve the same result.
Class Adapter – Implementation
Here is the implementation of the class adapter:
package com.journaldev.design.adapter;
// Using inheritance for the 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);
}
}
Object Adapter – Implementation
Here is the implementation of the object adapter:
package com.journaldev.design.adapter;
public class SocketObjectAdapterImpl implements SocketAdapter {
// Using composition for the 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);
}
}
Note that both adapter implementations are nearly identical and can implement the SocketAdapter
interface. The adapter interface can also be an abstract class.
Test Program
Here is a test program to use our Adapter Design Pattern implementation:
package com.journaldev.design.test;
import com.journaldev.design.adapter.SocketAdapter;
import com.journaldev.design.adapter.SocketClassAdapterImpl;
import com.journaldev.design.adapter.SocketObjectAdapterImpl;
import com.journaldev.design.adapter.Volt;
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 the above test program, we get the 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
Summary
The Adapter Design Pattern allows two unrelated interfaces to work together by placing an adapter object between them. In this blog post, we have seen how the Adapter Pattern can be implemented in Java, both as a class adapter and as an object adapter. This design pattern is particularly useful when dealing with legacy code or third-party interfaces.
Examples in the JDK
Some examples of the Adapter Design Pattern in JDK classes are:
java.util.Arrays#asList()
java.io.InputStreamReader(InputStream)
(returns a Reader)java.io.OutputStreamWriter(OutputStream)
(returns a Writer)