Design Pattern Adapter

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.

Developpeur et architecte passionné, qui souhaite partagé son univers et ses découvertes afin de rendre les choses plus simple pour chacun