JavaRush /Blog Java /Random-FR /Quels problèmes le modèle de conception de l’adaptateur r...

Quels problèmes le modèle de conception de l’adaptateur résout-il ?

Publié dans le groupe Random-FR
Le développement de logiciels est souvent compliqué par l'incompatibilité entre les composants qui fonctionnent les uns avec les autres. Par exemple, si vous devez intégrer une nouvelle bibliothèque avec une ancienne plateforme écrite dans des versions antérieures de Java, vous risquez de rencontrer une incompatibilité d'objets, ou plus précisément d'interfaces. Que faire dans ce cas ? Réécrire le code ? Mais c'est impossible : l'analyse du système prendra beaucoup de temps ou la logique interne du travail sera brisée. Quels problèmes le modèle de conception de l'adaptateur résout-il - 1Pour résoudre ce problème, ils ont mis au point le modèle Adapter, qui permet aux objets dotés d'interfaces incompatibles de fonctionner ensemble. Voyons comment l'utiliser !

Plus de détails sur le problème

Tout d’abord, simulons le comportement de l’ancien système. Disons que cela génère des raisons d'être en retard au travail ou à l'école. Pour ce faire, nous disposons d’une interface Excusequi contient les méthodes generateExcuse(), likeExcuse()et dislikeExcuse().
public interface Excuse {
   String generateExcuse();
   void likeExcuse(String excuse);
   void dislikeExcuse(String excuse);
}
Cette interface est implémentée par la classe WorkExcuse:
public class WorkExcuse implements Excuse {
   private String[] reasonOptions = {"по невероятному стечению обстоятельств у нас в доме закончилась горячая вода и я ждал, пока солнечный свет, сконцентрированный через лупу, нагреет кружку воды, чтобы я мог умыться.",
   "искусственный интеллект в моем будильнике подвел меня и разбудил на час раньше обычного. Поскольку сейчас зима, я думал что еще ночь и уснул. Дальше все How в тумане.",
   "предпраздничное настроение замедляет метаболические процессы в моем организме и приводит к подавленному состоянию и бессоннице."};
   private String[] sorryOptions = {"Это, конечно, не повторится, мне очень жаль.", "Прошу меня извинить за непрофессиональное поведение.", "Нет оправдания моему поступку. Я недостоин этой должности."};

   @Override
   public String generateExcuse() { // Случайно выбираем отговорку из массива
       String result = "Я сегодня опоздал, потому что " + reasonOptions[(int) Math.round(Math.random() + 1)] + "\n" +
               sorryOptions[(int) Math.round(Math.random() + 1)];
       return result;
   }

   @Override
   public void likeExcuse(String excuse) {
       // Дублируем элемент в массиве, чтобы шанс его выпадения был выше
   }

   @Override
   public void dislikeExcuse(String excuse) {
       // Удаляем элемент из массива
   }
}
Testons l'exemple :
Excuse excuse = new WorkExcuse();
System.out.println(excuse.generateExcuse());
Conclusion:
Я сегодня опоздал, потому что предпраздничное настроение замедляет метаболические процессы в моем организме и приводит к подавленному состоянию и бессоннице. 
Прошу меня извинить за непрофессиональное поведение.
Imaginons maintenant que vous ayez lancé le service, collecté des statistiques et remarqué que la majorité des utilisateurs du service sont des étudiants universitaires. Pour l'améliorer pour répondre aux besoins de ce groupe, vous avez commandé un système de génération d'excuses à un autre développeur spécialement pour elle. L'équipe de développement a mené des recherches, compilé des évaluations, connecté l'intelligence artificielle, ajouté l'intégration avec les embouteillages, la météo, etc. Vous disposez désormais d'une bibliothèque pour générer des excuses pour les étudiants, mais l'interface pour interagir avec elle est différente -StudentExcuse :
public interface StudentExcuse {
   String generateExcuse();
   void dislikeExcuse(String excuse);
}
L'interface dispose de deux méthodes : generateExcuse, qui génère une excuse, et dislikeExcuse, qui bloque l'excuse pour qu'elle n'apparaisse plus dans le futur. Une bibliothèque tierce est fermée pour modification - vous ne pouvez pas modifier son code source. En conséquence, dans votre système, il existe deux classes qui implémentent l'interface Excuseet une bibliothèque avec une classe SuperStudentExcusequi implémente l'interface StudentExcuse:
public class SuperStudentExcuse implements StudentExcuse {
   @Override
   public String generateExcuse() {
       // Логика нового функционала
       return "Невероятная отговорка, адаптированная под текущее состояние погоды, пробки or сбои в расписании общественного транспорта.";
   }

   @Override
   public void dislikeExcuse(String excuse) {
       // Добавляет причину в черный список
   }
}
Le code ne peut pas être modifié. Le schéma actuel ressemblera à ceci : Quels problèmes le modèle de conception Adapter - 2 résout-il ?Cette version du système fonctionne uniquement avec l'interface Excuse. Vous ne pouvez pas réécrire le code : dans une application volumineuse, de telles modifications peuvent prendre beaucoup de temps ou casser la logique de l'application. Vous pouvez proposer d'introduire l'interface principale et d'augmenter la hiérarchie : Quels problèmes le modèle de conception de l'adaptateur résout-il - 3Pour ce faire, vous devez renommer l'interface Excuse. Mais la hiérarchie supplémentaire n'est pas souhaitable dans les applications sérieuses : l'introduction d'un élément racine commun brise l'architecture. Vous devez implémenter une classe intermédiaire qui vous permettra d'utiliser les nouvelles et anciennes fonctionnalités avec une perte minimale. Bref, il vous faut un adaptateur .

Comment fonctionne le modèle d'adaptateur

Un adaptateur est un objet intermédiaire qui rend les appels aux méthodes sur un objet compréhensibles pour 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. Qu'il en soit ainsi Excuse. Grâce à cela, Middlewareil peut appeler les méthodes du premier objet. Middlewarereçoit les appels et les transmet au deuxième objet dans un format compatible. Voici à quoi ressemble l’implémentation d’une méthode Middlewareavec méthodes generateExcuseet dislikeExcuse:
public class Middleware implements Excuse { // 1. Middleware становится совместимым с an objectом WorkExcuse через интерфейс Excuse

   private StudentExcuse superStudentExcuse;

   public Middleware(StudentExcuse excuse) { // 2. Получаем ссылку на адаптируемый an object
       this.superStudentExcuse = excuse;
   }

   @Override
   public String generateExcuse() {
       return superStudentExcuse.generateExcuse(); // 3. Адаптер реализовывает метод интерфейса
   }

    @Override
    public void dislikeExcuse(String excuse) {
        // Метод предварительно помещает отговорку в черный список БД,
        // Затем передает ее в метод dislikeExcuse an object superStudentExcuse.
    }
   // Метод likeExcuse появятся позже
}
Tests (dans le code client) :
public class Test {
   public static void main(String[] args) {
       Excuse excuse = new WorkExcuse(); // Создаются an objectы классов,
       StudentExcuse newExcuse = new SuperStudentExcuse(); // Которые должны быть совмещены.
       System.out.println("Обычная причина для работника:");
       System.out.println(excuse.generateExcuse());
       System.out.println("\n");
       Excuse adaptedStudentExcuse = new Middleware(newExcuse); // Оборачиваем новый функционал в an object-адаптер
       System.out.println("Использование нового функционала с помощью адаптера:");
       System.out.println(adaptedStudentExcuse.generateExcuse()); // Адаптер вызывает адаптированный метод
   }
}
Conclusion:
Обычная причина для работника:
Я сегодня опоздал, потому что предпраздничное настроение замедляет метаболические процессы в моем организме и приводит к подавленному состоянию и бессоннице.
Нет оправдания моему поступку. Я недостоин этой должности. Использование нового функционала с помощью адаптера
Une excuse incroyable, adaptée aux conditions météorologiques actuelles, aux embouteillages ou aux perturbations des horaires des transports en commun. La méthode generateExcusetransfère simplement l'appel vers un autre objet, sans transformations supplémentaires. La méthode dislikeExcusenécessitait d’abord de placer l’excuse sur une liste noire de base de données. Un traitement de données intermédiaire supplémentaire est la raison pour laquelle le modèle Adapter est apprécié. Mais qu'en est-il d'une méthode likeExcusequi se trouve dans l'interface Excuse, mais pas dans StudentExcuse? Cette opération n'est pas prise en charge dans la nouvelle fonctionnalité. Pour un tel cas, ils ont trouvé une exception UnsupportedOperationException: elle est levée si l'opération demandée n'est pas prise en charge. Utilisons ça. Voici à quoi ressemble la nouvelle implémentation de classeMiddleware :
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("Метод likeExcuse не поддерживается в новом функционале");
   }

   @Override
   public void dislikeExcuse(String excuse) {
       // Метод обращается за дополнительной информацией к БД,
       // Затем передает ее в метод dislikeExcuse an object superStudentExcuse.
   }
}
À première vue, cette solution ne semble pas réussie, mais simuler la fonctionnalité peut conduire à une situation plus complexe. Si le client est attentif et que l'adaptateur est bien documenté, cette solution est acceptable.

Quand utiliser l'adaptateur

  1. Si vous devez utiliser une classe tierce, mais que son interface n'est pas compatible avec l'application principale. L'exemple ci-dessus montre comment un objet shim est créé qui encapsule les appels dans un format compréhensible pour l'objet cible.

  2. Lorsque plusieurs sous-classes existantes doivent avoir des fonctionnalités communes. Au lieu de sous-classes supplémentaires (leur création 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, et s'il existe un grand nombre de points incompatibles, leur nombre peut atteindre des tailles incontrôlables.

A ne pas confondre avec Façade et Décorateur

Après un examen superficiel, Adapter peut être confondu avec les motifs Façade et Decorator. La différence entre un adaptateur et une façade est qu'une façade introduit une nouvelle interface et enveloppe un sous-système entier. Eh bien, le décorateur, contrairement à l'adaptateur, modifie l'objet lui-même, pas l'interface.

Algorithme de mise en œuvre étape par étape

  1. Tout d’abord, assurez-vous qu’il existe un problème que ce modèle peut résoudre.

  2. Définissez une interface client pour le compte de laquelle une autre classe sera utilisée.

  3. Implémentez une classe d'adaptateur basée sur l'interface définie à l'étape précédente.

  4. Dans la classe adaptateur, créez un champ qui stocke une référence à l'objet. Cette référence est passée dans le constructeur.

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

    • Transférer l'appel sans modification ;

    • Modifiez les données, augmentez/diminuez le nombre d'appels à la méthode cible, élargissez davantage la composition des données, etc.

    • En dernier recours, si une méthode spécifique est incompatible, lancez une UnsupportedOperationException, qui doit strictement être documentée.

  6. Si l'application utilise l'adaptateur uniquement via l'interface client (comme dans l'exemple ci-dessus), cela permettra d'étendre les adaptateurs sans problème à l'avenir.

Bien sûr, un modèle de conception n'est pas une panacée à tous les maux, mais avec son aide, vous pouvez résoudre avec élégance le problème de l'incompatibilité des objets avec différentes interfaces. Un développeur qui connaît les modèles de base est plusieurs crans au-dessus de ceux qui savent simplement écrire des algorithmes, car ils sont nécessaires pour créer des applications sérieuses. Réutiliser le code devient moins difficile et la maintenance est un plaisir. C'est tout pour aujourd'hui! Mais nous continuerons bientôt notre connaissance de différents modèles de conception :)
Commentaires
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION