Description
The adapter design pattern allows objects with incompatible interfaces to interact with each other. In other words, it allows objects or classes to be adapted to each other. There are two uses of this pattern: objects and classes.
- Object: it is based on the principle of composition, where the adapter implements the interface of one object and encapsulates another.
- Class: inheritance is used, where the adapter inherits the interface of both objects, but this only works for languages that accept multiple inheritance.
Mise en situation
🔌 You have multiple devices that consume different amounts of voltage, the cable that connects them needs an adapter to function at the correct voltage, so we have:
âš¡ Devices at 1 volt. âš¡ Devices at 3 volts. âš¡ Devices at 12 volts. âš¡ Devices at 120 volts.
The first idea would be to develop the adapter object and pass the voltage through the constructor. This implementation works, but the problem is in the long run:
We want to add features that cannot adapt with the implemented logic, so we have to rewrite everything to make it adaptable.
Advantages and Disadvantages
Advantages | Disadvantages |
---|---|
Decoupling: each object has its own responsibility, each interface or converter is independent of the business logic. | Complexity by adding a set of new classes and interfaces. |
Flexibility: adding a new type of data is done directly by adding a new type of adapter without touching the client's code. |
When to use it?
- You want to use existing classes whose interface is incompatible with existing code.
- You want to reuse existing subclasses and add common functionality that the parent class cannot implement.
UML Diagram
Object
Class
Cas d'utilisation
We have two use cases:
- Objects: this solution uses interface implementation, which is the most common in object-oriented solutions.
- Classes: this solution is not really applicable in the long run because it will use multiple inheritance. (We won't go into further detail)
Implementation
First, we will create our Volt model that will be used in several of our classes:
Volt
package com.design.adapter.model;
public class Volt {
private int volts;
public Volt(int volts) {
this.volts = volts;
}
public int getVolts() {
return volts;
}
public void setVolts(int volts) {
this.volts = volts;
}
}
Now that our model is defined, we can create the interface that will be implemented by our adapter:
SocketAdapter
package com.design.adapter.interfaces;
import com.design.adapter.model.Volt;
public interface SocketAdapter {
Volt get120Volts();
Volt get12Volts();
Volt get3Volts();
Volt get1Volts();
}
We have defined the different methods that represent the voltages that we can obtain. We need our socket model in order to implement it in our adapter:
Socket
package com.design.adapter.model;
public class Socket {
public Volt getVolt() {
return new Volt(120);
}
}
Now, let's implement our adapter so that it can convert our volts:
SocketObjectAdapterImpl
package com.design.adapter.model;
import com.design.adapter.interfaces.SocketAdapter;
public class SocketObjectAdapterImpl implements SocketAdapter {
//Using composition for adapter pattern
private Socket socket = new Socket();
private Volt convertVolt(Volt v, int i) {
return new Volt(v.getVolts()/i);
}
@Override
public Volt get120Volts() {
return socket.getVolt();
}
@Override
public Volt get12Volts() {
return convertVolt(socket.getVolt(), 10);
}
@Override
public Volt get3Volts() {
return convertVolt(socket.getVolt(), 40);
}
@Override
public Volt get1Volts() {
return convertVolt(socket.getVolt(), 120);
}
}
Our conversion methods are implemented:
- By default, we will return the highest voltages.
convert_volt
allows us to convert our volts so that they can adapt.- The different voltages return the correct value depending on the call.
Now, let's test our methods:
Main
package com.design.adapter;
import com.design.adapter.interfaces.SocketAdapter;
import com.design.adapter.model.SocketObjectAdapterImpl;
import com.design.adapter.model.Volt;
public class Main {
public static void main(String[] args) {
testingObjectAdapter();
}
private static void testingObjectAdapter() {
SocketAdapter socketAdapter = new SocketObjectAdapterImpl();
Volt v3 = getVolt(socketAdapter, 3);
Volt v12 = getVolt(socketAdapter, 12);
Volt v1 = getVolt(socketAdapter, 1);
Volt v120 = getVolt(socketAdapter, 120);
System.out.println("V1 volts is using Object Adapter: " + v1.getVolts());
System.out.println("V3 volts is using Object Adapter: " + v3.getVolts());
System.out.println("V12 volts is using Object Adapter: " + v12.getVolts());
System.out.println("V120 volts is using Object Adapter: " + v120.getVolts());
}
private static Volt getVolt(SocketAdapter socketAdapter, int voltage) {
switch (voltage) {
case 1 : return socketAdapter.get1Volts();
case 3: return socketAdapter.get3Volts();
case 12: return socketAdapter.get12Volts();
default: return socketAdapter.get120Volts();
}
}
}
Result :
V1 volts is using Object Adapter: 1
V3 volts is using Object Adapter: 3
V12 volts is using Object Adapter: 12
V120 volts is using Object Adapter: 120
Ce que nous pouvons remarquer, c'est que le Design Pattern Adapter permet d'adapter des objets ou des classes qui n'ont pas une interface compatible. En utilisant l'objet Adapter, on peut encapsuler un objet avec une interface incompatible, afin de le rendre compatible avec l'interface du client. Cela permet d'augmenter la flexibilité et la séparation des tâches dans la logique métier. De plus, on peut facilement ajouter des objets et les traiter dans notre adaptateur, ce qui rend le code évolutif et facilement maintenable.