Description
Le design pattern adapter permet de faire interagir des objets ensemble dont l'interface de ceux-ci est normalement incompatible. Autrement dit, il permet d'adapter des objets ou des classes entre eux. Car oui il se divise en deux utilisations : les objets, mais aussi les classes :
- Objet: elle se base sur le principe de composition, l’adaptateur implémente l’interface d’un objet et en encapsule un autre.
- Classe: on utilise l'héritage, l’adaptateur hérite de l’interface des deux objets, mais cela ne marche que pour les langages acceptant l'héritage multiple.
Mise en situation
🔌 Vous avez plusieurs appareils qui consomment une quantité de volts différents, le câble qui les relie a besoin d'un adaptateur pour fonctionner au bon voltage, on a donc :
⚡ Des appareils à 1 volt. ⚡ Des appareils à 3 volts. ⚡ Des appareils à 12 volts. ⚡ Des appareils à 120 volts.
La première idée serait de développer l'objet de l'adaptateur et lui passer le voltage à travers le constructeur. Cette implémentation fonctionne, mais le souci est sur le long terme :
On veut rajouter des fonctionnalités qui ne peuvent pas s'adapter avec la logique implémentée, il faut donc tout réécrire pour faire en sorte que cela s'adapte.
Avantages et inconvénients
Avantages | Inconvénients |
---|---|
Découplage: chaque objet a sa propre responsabilité, chaque interface ou convertisseur est alors indépendant de la logique métier. | Complexité par l'ajout d'un ensemble de nouvelles classes et interfaces. |
Flexibilité: l'ajout d'un nouveau type de données se fait directement par l'ajout d'un nouveau type d'adaptateur sans toucher au code du client. |
Quand l'utiliser ?
- Vous voulez utiliser des classes existantes dont l'interface est incompatible avec le code existant.
- Vous voulez réutiliser des sous-classes existantes et y ajouter des fonctionnalités communes que la classe mère ne peut implémenter.
Diagramme UML
Objet
Class
Cas d'utilisation
Nous avons deux cas d'utilisation :
- Les objets : cette solution utilise l'implémentation de l'interface, qui est la plus fréquente dans les solutions orientées objets.
- Les classes : cette solution n'est pas vraiment applicable étant donné que sur le long terme elle utilisera de l'héritage multiple. (Nous n'allons pas nous étendre plus dessus)
Implémentation
Nous allons tout d'abord créer notre modèle Volt qui sera utilisé dans plusieurs de nos 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;
}
}
Notre modèle définit nous pouvons créer l'interface qui sera implémenter par notre adaptateur :
SocketAdapter
package com.design.adapter.interfaces;
import com.design.adapter.model.Volt;
public interface SocketAdapter {
Volt get120Volts();
Volt get12Volts();
Volt get3Volts();
Volt get1Volts();
}
Nous avons défini les différentes méthodes qui représentent nos tensions que l'on peut obtenir. Il nous faut notre modèle socket afin de pouvoir l'implémenter dans notre adaptateur :
Socket
package com.design.adapter.model;
public class Socket {
public Volt getVolt() {
return new Volt(120);
}
}
Maintenant, implémentons notre adaptateur afin qu'il convertisse nos volts :
SocketAdapterImpl
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);
}
}
Nos méthodes de conversion sont implémentées :
- Par défaut, on retournera les voltages les plus élevés.
convertVolt
nous permettra de faire la conversion de nos volts afin qu'ils s'adaptent.- Les différentes tensions retournent la bonne valeur en fonction de l'appel.
Il ne nous reste plus qu'à tester nos méthodes :
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();
}
}
}
Resultat :
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.