Design patterns
Adaptateur
Recherche…
Modèle d'adaptateur (PHP)
Un exemple réel utilisant une expérience scientifique où certaines routines sont effectuées sur différents types de tissus. La classe contient deux fonctions par défaut pour obtenir le tissu ou la routine séparément. Dans une version ultérieure, nous l’avons ensuite adaptée en utilisant une nouvelle classe pour ajouter une fonction qui permet les deux. Cela signifie que nous n'avons pas édité le code d'origine et que nous ne courons donc aucun risque de casser notre classe existante (et de ne pas procéder à de nouveaux tests).
class Experiment {
private $routine;
private $tissue;
function __construct($routine_in, $tissue_in) {
$this->routine = $routine_in;
$this->tissue = $tissue_in;
}
function getRoutine() {
return $this->routine;
}
function getTissue() {
return $this->tissue;
}
}
class ExperimentAdapter {
private $experiment;
function __construct(Experiment $experiment_in) {
$this->experiment = $experiment_in;
}
function getRoutineAndTissue() {
return $this->experiment->getTissue().' ('. $this->experiment->getRoutine().')';
}
}
Adaptateur (Java)
Supposons que dans votre base de code actuelle, il existe MyLogger
interface MyLogger
comme celle-ci:
interface MyLogger {
void logMessage(String message);
void logException(Throwable exception);
}
Disons que vous avez créé quelques implémentations concrètes, telles que MyFileLogger
et MyConsoleLogger
.
Vous avez décidé d'utiliser un cadre pour contrôler la connectivité Bluetooth de votre application. Ce framework contient un BluetoothManager
avec le constructeur suivant:
class BluetoothManager {
private FrameworkLogger logger;
public BluetoothManager(FrameworkLogger logger) {
this.logger = logger;
}
}
Le BluetoothManager
accepte également un enregistreur, ce qui est génial! Cependant, il attend un enregistreur dont l'interface a été définie par le framework et qui a utilisé une surcharge de méthode au lieu de nommer ses fonctions différemment:
interface FrameworkLogger {
void log(String message);
void log(Throwable exception);
}
Vous souhaitez déjà réutiliser un MyLogger
implémentations MyLogger
, mais elles ne correspondent pas à l'interface de FrameworkLogger
. C'est là que le modèle de conception de l'adaptateur entre en jeu:
class FrameworkLoggerAdapter implements FrameworkLogger {
private MyLogger logger;
public FrameworkLoggerAdapter(MyLogger logger) {
this.logger = logger;
}
@Override
public void log(String message) {
this.logger.logMessage(message);
}
@Override
public void log(Throwable exception) {
this.logger.logException(exception);
}
}
En définissant une classe d'adaptateur qui implémente l'interface FrameworkLogger
et accepte une implémentation MyLogger
la fonctionnalité peut être mappée entre les différentes interfaces. Maintenant, il est possible d'utiliser BluetoothManager
avec toutes les implémentations MyLogger
comme ceci:
FrameworkLogger fileLogger = new FrameworkLoggerAdapter(new MyFileLogger());
BluetoothManager manager = new BluetoothManager(fileLogger);
FrameworkLogger consoleLogger = new FrameworkLoggerAdapter(new MyConsoleLogger());
BluetoothManager manager2 = new BluetoothManager(consoleLogger);
Exemple Java
Un excellent exemple existant du modèle d'adaptateur peut être trouvé dans les classes SWT MouseListener et MouseAdapter .
L'interface MouseListener se présente comme suit:
public interface MouseListener extends SWTEventListener {
public void mouseDoubleClick(MouseEvent e);
public void mouseDown(MouseEvent e);
public void mouseUp(MouseEvent e);
}
Imaginez maintenant un scénario où vous construisez une interface utilisateur et ajoutez ces écouteurs, mais la plupart du temps, vous ne vous souciez de rien d'autre que lorsque vous cliquez sur un seul clic (mouseUp). Vous ne voudriez pas créer constamment des implémentations vides:
obj.addMouseListener(new MouseListener() {
@Override
public void mouseDoubleClick(MouseEvent e) {
}
@Override
public void mouseDown(MouseEvent e) {
}
@Override
public void mouseUp(MouseEvent e) {
// Do the things
}
});
Au lieu de cela, nous pouvons utiliser MouseAdapter:
public abstract class MouseAdapter implements MouseListener {
public void mouseDoubleClick(MouseEvent e) { }
public void mouseDown(MouseEvent e) { }
public void mouseUp(MouseEvent e) { }
}
En fournissant des implémentations vides par défaut, nous sommes libres de ne remplacer que les méthodes qui nous intéressent de l’adaptateur. En suivant l'exemple ci-dessus:
obj.addMouseListener(new MouseAdapter() {
@Override
public void mouseUp(MouseEvent e) {
// Do the things
}
});
Adaptateur (UML et exemple de situation)
Pour utiliser le modèle d'adaptateur et le type de situation où il peut être appliqué, un exemple simple, très simple et très concret est donné ici. Il n'y aura pas de code ici, juste UML et une description de l'exemple de situation et de son problème. Certes, le contenu UML est écrit comme Java. (Eh bien, le texte de conseil a dit "Les bons exemples sont principalement du code", je pense que les modèles de conception sont assez abstraits pour être introduits d'une manière différente.)
En général, le modèle d'adaptateur est une solution adéquate pour une situation où vous avez des interfaces incompatibles et qu'aucun d'entre eux ne peut être réécrit directement.
Imaginez que vous dirigez un bon petit service de livraison de pizza. Les clients peuvent commander en ligne sur votre site Web et vous avez un petit système utilisant une classe Pizza
pour représenter vos pizzas et calculer des factures, des rapports fiscaux et plus encore. Le prix de vos pizzas est donné sous la forme d’un entier représentant le prix en cent (de la devise de votre choix).
Votre service de distribution fonctionne très bien, mais à un moment donné, vous ne pouvez plus gérer vous-même le nombre croissant de clients, mais vous souhaitez tout de même vous développer. Vous décidez d'ajouter vos pizzas au menu d'un grand service de livraison de méta en ligne. Ils offrent beaucoup de repas différents - pas seulement des pizzas - de sorte que leur système utilise plus l'abstraction et dispose d'une interface IMeal
représentant les repas accompagnant une classe MoneyAmount
représentant de l'argent.
MoneyAmount
compose de deux entiers en entrée, un pour le montant (ou une devise aléatoire) avant la virgule, et un pour le montant en cents de 0 à 99 après la virgule;
Étant donné que le prix de votre Pizza
est un nombre entier représentant le prix total en un centime (> 99), il n’est pas compatible avec IMeal
. C'est à ce moment-là qu'intervient le modèle d'adaptateur: au cas où il vous faudrait trop d'effort pour changer votre propre système ou en créer un nouveau et que vous devez implémenter une interface incompatible, vous pouvez appliquer le modèle d'adaptateur.
Il existe deux manières d'appliquer le modèle: l'adaptateur de classe et l'adaptateur d'objet.
Les deux ont en commun qu'un adaptateur ( PizzaAdapter
) fonctionne comme une sorte de traducteur entre la nouvelle interface et l'adaptee ( Pizza
dans cet exemple). L'adaptateur implémente la nouvelle interface ( IMeal
), puis hérite de Pizza
et convertit son propre prix d'un entier à deux (adaptateur de classe)
ou possède un objet de type Pizza
comme attribut et convertit les valeurs de cet objet (adaptateur d'objet).
En appliquant le modèle d'adaptateur, vous allez "traduire" entre des interfaces incompatibles.