JavaRush /Java-Blog /Random-DE /Welche Probleme löst das Adapter-Entwurfsmuster?

Welche Probleme löst das Adapter-Entwurfsmuster?

Veröffentlicht in der Gruppe Random-DE
Die Softwareentwicklung wird oft durch die Inkompatibilität zwischen miteinander arbeitenden Komponenten erschwert. Wenn Sie beispielsweise eine neue Bibliothek in eine alte Plattform integrieren müssen, die in früheren Java-Versionen geschrieben wurde, kann es zu Inkompatibilitäten von Objekten oder genauer gesagt Schnittstellen kommen. Was ist in diesem Fall zu tun? Den Code umschreiben? Dies ist jedoch unmöglich: Die Analyse des Systems wird viel Zeit in Anspruch nehmen oder die interne Logik der Arbeit wird gestört. Welche Probleme löst das Adapter-Entwurfsmuster - 1Um dieses Problem zu lösen, haben sie das Adaptermuster entwickelt, das die Zusammenarbeit von Objekten mit inkompatiblen Schnittstellen unterstützt. Mal sehen, wie man es benutzt!

Weitere Details zum Problem

Lassen Sie uns zunächst das Verhalten des alten Systems simulieren. Nehmen wir an, es generiert Gründe dafür, dass man zu spät zur Arbeit oder zur Schule kommt. Dazu verfügen wir über eine Schnittstelle Excuse, die die Methoden generateExcuse(), likeExcuse()und enthält dislikeExcuse().
public interface Excuse {
   String generateExcuse();
   void likeExcuse(String excuse);
   void dislikeExcuse(String excuse);
}
Diese Schnittstelle wird von der Klasse implementiert WorkExcuse:
public class WorkExcuse implements Excuse {
   private String[] reasonOptions = {"по невероятному стечению обстоятельств у нас в доме закончилась горячая вода и я ждал, пока солнечный свет, сконцентрированный через лупу, нагреет кружку воды, чтобы я мог умыться.",
   "искусственный интеллект в моем будильнике подвел меня и разбудил на час раньше обычного. Поскольку сейчас зима, я думал что еще ночь и уснул. Дальше все Wie в тумане.",
   "предпраздничное настроение замедляет метаболические процессы в моем организме и приводит к подавленному состоянию и бессоннице."};
   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) {
       // Удаляем элемент из массива
   }
}
Testen wir das Beispiel:
Excuse excuse = new WorkExcuse();
System.out.println(excuse.generateExcuse());
Abschluss:
Я сегодня опоздал, потому что предпраздничное настроение замедляет метаболические процессы в моем организме и приводит к подавленному состоянию и бессоннице. 
Прошу меня извинить за непрофессиональное поведение.
Stellen wir uns nun vor, Sie hätten den Dienst gestartet, Statistiken gesammelt und festgestellt, dass die Mehrheit der Dienstnutzer Universitätsstudenten sind. Um es für die Bedürfnisse dieser Gruppe zu verbessern, haben Sie bei einem anderen Entwickler speziell für sie ein Entschuldigungsgenerierungssystem bestellt. Das Entwicklungsteam führte Recherchen durch, stellte Bewertungen zusammen, vernetzte künstliche Intelligenz, fügte die Integration mit Staus, Wetter usw. hinzu. Jetzt haben Sie eine Bibliothek zum Generieren von Ausreden für Schüler, aber die Schnittstelle für die Interaktion damit ist anders StudentExcuse:
public interface StudentExcuse {
   String generateExcuse();
   void dislikeExcuse(String excuse);
}
Die Schnittstelle verfügt über zwei Methoden: generateExcuse, die eine Entschuldigung generiert, und dislikeExcuse, die die Entschuldigung blockiert, sodass sie in Zukunft nicht mehr angezeigt wird. Eine Drittanbieterbibliothek ist zur Bearbeitung geschlossen – Sie können ihren Quellcode nicht ändern. Daher gibt es in Ihrem System zwei Klassen, die die Schnittstelle implementieren Excuse, und eine Bibliothek mit einer Klasse SuperStudentExcuse, die die Schnittstelle implementiert StudentExcuse:
public class SuperStudentExcuse implements StudentExcuse {
   @Override
   public String generateExcuse() {
       // Логика нового функционала
       return "Невероятная отговорка, адаптированная под текущее состояние погоды, пробки oder сбои в расписании общественного транспорта.";
   }

   @Override
   public void dislikeExcuse(String excuse) {
       // Добавляет причину в черный список
   }
}
Der Code kann nicht geändert werden. Das aktuelle Schema sieht folgendermaßen aus: Welche Probleme löst das Adapter-2-Entwurfsmuster?Diese Version des Systems funktioniert nur mit der Excuse-Schnittstelle. Sie können den Code nicht neu schreiben: In einer großen Anwendung können solche Änderungen lange dauern oder die Anwendungslogik zerstören. Sie können vorschlagen, die Hauptschnittstelle einzuführen und die Hierarchie zu erhöhen: Welche Probleme löst das Adapter-Entwurfsmuster - 3Dazu müssen Sie die Schnittstelle umbenennen Excuse. Bei ernsthaften Anwendungen ist die zusätzliche Hierarchie jedoch unerwünscht: Die Einführung eines gemeinsamen Wurzelelements zerstört die Architektur. Sie sollten eine Zwischenklasse implementieren, die es Ihnen ermöglicht, neue und alte Funktionen mit minimalem Verlust zu nutzen. Kurz gesagt, Sie benötigen einen Adapter .

So funktioniert das Adaptermuster

Ein Adapter ist ein Zwischenobjekt, das Aufrufe von Methoden eines Objekts für ein anderes Objekt verständlich macht. Lassen Sie uns für unser Beispiel einen Adapter implementieren und ihn nennen Middleware. Unser Adapter muss eine Schnittstelle implementieren, die mit einem der Objekte kompatibel ist. Lass es sein Excuse. Dadurch Middlewarekann es Methoden des ersten Objekts aufrufen. Middlewareempfängt Aufrufe und leitet sie in einem kompatiblen Format an das zweite Objekt weiter. MiddlewareSo sieht die Implementierung einer Methode mit Methoden generateExcuseund aus dislikeExcuse:
public class Middleware implements Excuse { // 1. Middleware становится совместимым с ein Objektом WorkExcuse через интерфейс Excuse

   private StudentExcuse superStudentExcuse;

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

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

    @Override
    public void dislikeExcuse(String excuse) {
        // Метод предварительно помещает отговорку в черный список БД,
        // Затем передает ее в метод dislikeExcuse ein Objektа superStudentExcuse.
    }
   // Метод likeExcuse появятся позже
}
Testen (im Client-Code):
public class Test {
   public static void main(String[] args) {
       Excuse excuse = new WorkExcuse(); // Создаются ein Objektы классов,
       StudentExcuse newExcuse = new SuperStudentExcuse(); // Которые должны быть совмещены.
       System.out.println("Обычная причина для работника:");
       System.out.println(excuse.generateExcuse());
       System.out.println("\n");
       Excuse adaptedStudentExcuse = new Middleware(newExcuse); // Оборачиваем новый функционал в ein Objekt-адаптер
       System.out.println("Использование нового функционала с помощью адаптера:");
       System.out.println(adaptedStudentExcuse.generateExcuse()); // Адаптер вызывает адаптированный метод
   }
}
Abschluss:
Обычная причина для работника:
Я сегодня опоздал, потому что предпраздничное настроение замедляет метаболические процессы в моем организме и приводит к подавленному состоянию и бессоннице.
Нет оправдания моему поступку. Я недостоин этой должности. Использование нового функционала с помощью адаптера
Eine unglaubliche Ausrede, angepasst an die aktuellen Wetterbedingungen, Staus oder Störungen im ÖPNV-Fahrplan. Die Methode generateExcuseleitet den Aufruf einfach an ein anderes Objekt weiter, ohne zusätzliche Transformationen. Die Methode dislikeExcuseerforderte zunächst das Platzieren der Entschuldigung auf einer Datenbank-Blacklist. Die zusätzliche Zwischendatenverarbeitung ist der Grund, warum das Adaptermuster so beliebt ist. Aber was ist mit einer Methode likeExcuse, die sich in der Schnittstelle befindet Excuse, aber nicht in StudentExcuse? Dieser Vorgang wird in der neuen Funktionalität nicht unterstützt. Für einen solchen Fall haben sie sich eine Ausnahme ausgedacht UnsupportedOperationException: Sie wird ausgelöst, wenn der angeforderte Vorgang nicht unterstützt wird. Nutzen wir das. So sieht die neue Klassenimplementierung aus Middleware:
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 ein Objektа superStudentExcuse.
   }
}
Auf den ersten Blick scheint diese Lösung nicht erfolgreich zu sein, aber die Simulation der Funktionalität kann zu einer komplexeren Situation führen. Wenn der Kunde aufmerksam ist und der Adapter gut dokumentiert ist, ist diese Lösung akzeptabel.

Wann sollte der Adapter verwendet werden?

  1. Wenn Sie eine Drittanbieterklasse verwenden müssen, deren Schnittstelle jedoch nicht mit der Hauptanwendung kompatibel ist. Das obige Beispiel zeigt, wie ein Shim-Objekt erstellt wird, das Aufrufe in ein Format umschließt, das für das Zielobjekt verständlich ist.

  2. Wenn mehrere vorhandene Unterklassen eine gemeinsame Funktionalität haben müssen. Anstelle zusätzlicher Unterklassen (deren Erstellung führt zu Codeduplizierung) ist es besser, einen Adapter zu verwenden.

Vorteile und Nachteile

Vorteil: Der Adapter verbirgt vor dem Client die Details der Verarbeitung von Anfragen von einem Objekt zu einem anderen. Der Clientcode kümmert sich nicht um die Formatierung der Daten oder die Verarbeitung von Aufrufen der Zielmethode. Es ist zu kompliziert und Programmierer sind faul :) Nachteil: Die Codebasis des Projekts wird durch zusätzliche Klassen kompliziert, und wenn es eine große Anzahl inkompatibler Punkte gibt, kann deren Anzahl auf unkontrollierbare Größen anwachsen.

Nicht zu verwechseln mit Fassade und Dekorateur

Bei oberflächlicher Betrachtung kann Adapter mit den Mustern Façade und Decorator verwechselt werden. Der Unterschied zwischen einem Adapter und einer Fassade besteht darin, dass eine Fassade eine neue Schnittstelle einführt und ein gesamtes Subsystem umschließt. Nun, der Decorator ändert im Gegensatz zum Adapter das Objekt selbst, nicht die Schnittstelle.

Schritt-für-Schritt-Implementierungsalgorithmus

  1. Stellen Sie zunächst sicher, dass es ein Problem gibt, das dieses Muster lösen kann.

  2. Definieren Sie eine Client-Schnittstelle, in deren Namen eine andere Klasse verwendet wird.

  3. Implementieren Sie eine Adapterklasse basierend auf der im vorherigen Schritt definierten Schnittstelle.

  4. Erstellen Sie in der Adapterklasse ein Feld, das einen Verweis auf das Objekt speichert. Diese Referenz wird im Konstruktor übergeben.

  5. Implementieren Sie alle Clientschnittstellenmethoden im Adapter. Die Methode kann:

    • Den Anruf ohne Änderungen weiterleiten;

    • Ändern Sie Daten, erhöhen/verringern Sie die Anzahl der Aufrufe der Zielmethode, erweitern Sie die Zusammensetzung der Daten usw.

    • Wenn eine bestimmte Methode inkompatibel ist, lösen Sie als letzten Ausweg eine UnsupportedOperationException aus, die unbedingt dokumentiert werden muss.

  6. Wenn die Anwendung den Adapter nur über die Client-Schnittstelle verwendet (wie im Beispiel oben), können Adapter in Zukunft problemlos erweitert werden.

Natürlich ist ein Entwurfsmuster kein Allheilmittel für alle Übel, aber mit seiner Hilfe können Sie das Problem der Inkompatibilität von Objekten mit unterschiedlichen Schnittstellen elegant lösen. Ein Entwickler, der grundlegende Muster kennt, ist einem Entwickler, der lediglich weiß, wie man Algorithmen schreibt, einige Schritte voraus, da diese für die Erstellung seriöser Anwendungen benötigt werden. Die Wiederverwendung von Code wird einfacher und die Wartung wird zum Vergnügen. Das ist alles für heute! Aber wir werden unsere Bekanntschaft mit verschiedenen Designmustern bald fortsetzen :)
Kommentare
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION