CodeGym /Blog Java /Random-FR /Quels problèmes le modèle de conception d'adaptateur réso...
Auteur
Artem Divertitto
Senior Android Developer at United Tech

Quels problèmes le modèle de conception d'adaptateur résout-il ?

Publié dans le groupe Random-FR
Le développement de logiciels est rendu plus difficile par des composants incompatibles qui doivent fonctionner ensemble. Par exemple, si vous avez besoin d'intégrer une nouvelle bibliothèque avec une ancienne plate-forme écrite dans des versions antérieures de Java, vous pouvez rencontrer des objets incompatibles, ou plutôt des interfaces incompatibles. Quels problèmes le modèle de conception d'adaptateur résout-il ?  - 1Que faire dans ce cas ? Réécrire le code ? Nous ne pouvons pas faire cela, car l'analyse du système prendra beaucoup de temps ou la logique interne de l'application sera violée. Pour résoudre ce problème, le modèle d'adaptateur a été créé. Il aide les objets avec des interfaces incompatibles à travailler ensemble. Voyons comment l'utiliser !

En savoir plus sur le problème

Tout d'abord, nous allons simuler le comportement de l'ancien système. Supposons qu'il génère des excuses pour être en retard au travail ou à l'école. Pour ce faire, il a une Excuseinterface qui a generateExcuse(), likeExcuse()et dislikeExcuse()des méthodes.

public interface Excuse {
   String generateExcuse();
   void likeExcuse(String excuse);
   void dislikeExcuse(String excuse);
}
La WorkExcuseclasse implémente cette interface :

public class WorkExcuse implements Excuse {
   private String[] excuses = {"in an incredible confluence of circumstances, I ran out of hot water and had to wait until sunlight, focused using a magnifying glass, heated a mug of water so that I could wash.",
   "the artificial intelligence in my alarm clock failed me, waking me up an hour earlier than normal. Because it is winter, I thought it was still nighttime and I fell back asleep. Everything after that is a bit hazy.",
   "my pre-holiday mood slows metabolic processes in my body, leading to depression and insomnia."};
   private String [] apologies = {"This will not happen again, of course. I'm very sorry.", "I apologize for my unprofessional behavior.", "There is no excuse for my actions. I am not worthy of this position."};

   @Override
   public String generateExcuse() { // Randomly select an excuse from the array
       String result = "I was late today because " + excuses[(int) Math.round(Math.random() + 1)] + "\\n" +
               apologies[(int) Math.round(Math.random() + 1)];
       return result;
   }

   @Override
   public void likeExcuse(String excuse) {
       // Duplicate the element in the array so that its chances of being chosen are higher
   }

   @Override
   public void dislikeExcuse(String excuse) {
       // Remove the item from the array
   }
}
Testons notre exemple :

Excuse excuse = new WorkExcuse();
System.out.println(excuse.generateExcuse());
Sortir:

"I was late today because my pre-holiday mood slows metabolic processes in my body, leading to depression and insomnia.
I apologize for my unprofessional behavior.
Imaginez maintenant que vous avez lancé un service générateur d'excuses, collecté des statistiques et remarqué que la plupart de vos utilisateurs sont des étudiants universitaires. Pour mieux servir ce groupe, vous avez demandé à un autre développeur de créer un système qui génère des excuses spécifiquement pour les étudiants universitaires. L'équipe de développement a mené des études de marché, classé les excuses, connecté une intelligence artificielle et intégré le service avec des rapports de trafic, des bulletins météorologiques, etc. Vous avez maintenant une bibliothèque pour générer des excuses pour les étudiants universitaires, mais elle a une interface différente : StudentExcuse.

public interface StudentExcuse {
   String generateExcuse();
   void dislikeExcuse(String excuse);
}
Cette interface a deux méthodes : generateExcuse, qui génère une excuse, et dislikeExcuse, qui empêche l'excuse de réapparaître à l'avenir. La bibliothèque tierce ne peut pas être modifiée, c'est-à-dire que vous ne pouvez pas modifier son code source. Nous avons maintenant un système avec deux classes qui implémentent l' Excuseinterface, et une bibliothèque avec une SuperStudentExcuseclasse qui implémente l' StudentExcuseinterface :

public class SuperStudentExcuse implements StudentExcuse {
   @Override
   public String generateExcuse() {
       // Logic for the new functionality
       return "An incredible excuse adapted to the current weather conditions, traffic jams, or delays in public transport schedules.";
   }

   @Override
   public void dislikeExcuse(String excuse) {
       // Adds the reason to a blacklist
   }
}
Le code ne peut pas être modifié. La hiérarchie actuelle des classes ressemble à ceci : Quels problèmes le modèle de conception d'adaptateur résout-il ?  - 2Cette version du système ne fonctionne qu'avec l'interface Excuse. Vous ne pouvez pas réécrire le code : dans une application volumineuse, effectuer de telles modifications pourrait devenir un processus long ou casser la logique de l'application. Nous pourrions introduire une interface de base et étendre la hiérarchie : Quels problèmes le modèle de conception d'adaptateur résout-il ?  - 3Pour ce faire, nous devons renommer l' Excuseinterface. Mais la hiérarchie supplémentaire n'est pas souhaitable dans les applications sérieuses : l'introduction d'un élément racine commun casse l'architecture. Vous devez implémenter une classe intermédiaire qui nous permettra d'utiliser à la fois la nouvelle et l'ancienne fonctionnalité avec un minimum de pertes. Bref, il vous faut un adaptateur .

Le principe du modèle d'adaptateur

Un adaptateur est un objet intermédiaire qui permet aux appels de méthode d'un objet d'être compris par un autre. Implémentons un adaptateur pour notre exemple et appelons-le Middleware. Notre adaptateur doit implémenter une interface compatible avec l'un des objets. Laissez faire Excuse. Cela permet Middlewared'appeler les méthodes du premier objet. Middlewarereçoit les appels et les renvoie de manière compatible vers le second objet. Voici l' Middlewareimplémentation avec les méthodes generateExcuseet dislikeExcuse:

public class Middleware implements Excuse { // 1. Middleware becomes compatible with WorkExcuse objects via the Excuse interface

   private StudentExcuse superStudentExcuse;

   public Middleware(StudentExcuse excuse) { // 2. Get a reference to the object being adapted
       this.superStudentExcuse = excuse;
   }

   @Override
   public String generateExcuse() {
       return superStudentExcuse.generateExcuse(); // 3. The adapter implements an interface method
   }

    @Override
    public void dislikeExcuse(String excuse) {
        // The method first adds the excuse to the blacklist,
        // Then passes it to the dislikeExcuse method of the superStudentExcuse object.
    }
   // The likeExcuse method will appear later
}
Test (en code client) :

public class Test {
   public static void main(String[] args) {
       Excuse excuse = new WorkExcuse(); // We create objects of the classes
       StudentExcuse newExcuse = new SuperStudentExcuse(); // that must be compatible.
       System.out.println("An ordinary excuse for an employee:");
       System.out.println(excuse.generateExcuse());
       System.out.println("\n");
       Excuse adaptedStudentExcuse = new Middleware(newExcuse); // Wrap the new functionality in the adapter object
       System.out.println("Using new functionality with the adapter:");
       System.out.println(adaptedStudentExcuse.generateExcuse()); // The adapter calls the adapted method
   }
}
Sortir:

An ordinary excuse for an employee:
I was late today because my pre-holiday mood slows metabolic processes in my body, leading to depression and insomnia.
There is no excuse for my actions. I am not worthy of this position. Using new functionality with the adapter:
Une excuse incroyable adaptée aux conditions météorologiques actuelles, aux embouteillages ou aux retards dans les horaires des transports en commun. La generateExcuseméthode passe simplement l'appel à un autre objet, sans aucune modification supplémentaire. La dislikeExcuseméthode nous obligeait à mettre d'abord l'excuse sur liste noire. La possibilité d'effectuer un traitement de données intermédiaire est une raison pour laquelle les gens aiment le modèle d'adaptateur. Mais qu'en est-il de la likeExcuseméthode, qui fait partie de l' Excuseinterface mais pas de l' StudentExcuseinterface ? La nouvelle fonctionnalité ne prend pas en charge cette opération. Le UnsupportedOperationExceptiona été inventé pour cette situation. Elle est levée si l'opération demandée n'est pas prise en charge. Utilisons-le. Voici à quoi Middlewareressemble la nouvelle implémentation de la classe :

public class Middleware implements Excuse {

   private StudentExcuse superStudentExcuse;

   public Middleware(StudentExcuse excuse) {
       this.superStudentExcuse = excuse;
   }

   @Override
   public String generateExcuse() {
       return superStudentExcuse.generateExcuse();
   }

   @Override
   public void likeExcuse(String excuse) {
       throw new UnsupportedOperationException("The likeExcuse method is not supported by the new functionality");
   }

   @Override
   public void dislikeExcuse(String excuse) {
       // The method accesses a database to fetch additional information,
       // and then passes it to the superStudentExcuse object's dislikeExcuse method.
   }
}
À première vue, cette solution ne semble pas très bonne, mais imiter la fonctionnalité peut compliquer la situation. Si le client fait attention et que l'adaptateur est bien documenté, une telle solution est acceptable.

Quand utiliser un adaptateur

  1. Lorsque vous devez utiliser une classe tierce, mais que son interface est incompatible avec l'application principale. L'exemple ci-dessus montre comment créer un objet adaptateur qui encapsule les appels dans un format qu'un objet cible peut comprendre.

  2. Lorsque plusieurs sous-classes existantes ont besoin de fonctionnalités communes. Au lieu de créer des sous-classes supplémentaires (ce qui entraînera une duplication de code), il est préférable d'utiliser un adaptateur.

Avantages et inconvénients

Avantage : L'adaptateur cache au client les détails du traitement des requêtes d'un objet à un autre. Le code client ne pense pas au formatage des données ni à la gestion des appels à la méthode cible. C'est trop compliqué, et les programmeurs sont paresseux :) Inconvénient : La base de code du projet est compliquée par des classes supplémentaires. Si vous avez beaucoup d'interfaces incompatibles, le nombre de classes supplémentaires peut devenir ingérable.

Ne confondez pas un adaptateur avec une façade ou un décorateur

Avec seulement une inspection superficielle, un adaptateur pourrait être confondu avec les motifs de façade et de décorateur. La différence entre un adaptateur et une façade est qu'une façade introduit une nouvelle interface et enveloppe l'ensemble du sous-système. Et un décorateur, contrairement à un adaptateur, modifie l'objet lui-même plutôt que l'interface.

Algorithme pas à pas

  1. Tout d'abord, assurez-vous que vous avez un problème que ce modèle peut résoudre.

  2. Définissez l'interface client qui sera utilisée pour interagir indirectement avec des objets incompatibles.

  3. Faites en sorte que la classe d'adaptateur hérite de l'interface définie à l'étape précédente.

  4. Dans la classe adaptateur, créez un champ pour stocker une référence à l'objet adapté. Cette référence est transmise au constructeur.

  5. Implémentez toutes les méthodes d'interface client dans l'adaptateur. Une méthode peut :

    • Passer les appels sans apporter de modifications

    • Modifier ou compléter des données, augmenter/diminuer le nombre d'appels à la méthode cible, etc.

    • Dans les cas extrêmes, si une méthode particulière reste incompatible, lancez une UnsupportedOperationException. Les opérations non prises en charge doivent être strictement documentées.

  6. Si l'application utilise uniquement la classe d'adaptateur via l'interface client (comme dans l'exemple ci-dessus), l'adaptateur peut être étendu sans problème à l'avenir.

Bien sûr, ce design pattern n'est pas la panacée à tous les maux, mais il peut vous aider à résoudre avec élégance le problème d'incompatibilité entre des objets aux interfaces différentes. Un développeur qui connaît les modèles de base a plusieurs longueurs d'avance sur ceux qui ne savent écrire que des algorithmes, car les modèles de conception sont nécessaires pour créer des applications sérieuses. La réutilisation du code n'est pas si difficile et la maintenance devient agréable. C'est tout pour aujourd'hui! Mais nous continuerons bientôt à apprendre à connaître divers modèles de conception :)
Commentaires
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION