![Welche Probleme löst das Adapterentwurfsmuster? - 1](https://cdn.codegym.cc/images/article/2a00c183-20f3-4a73-abc5-f1f896e18a52/800.jpeg)
Mehr zum Problem
Zunächst simulieren wir das Verhalten des alten Systems. Angenommen, es generiert Ausreden dafür, dass man zu spät zur Arbeit oder zur Schule kommt. Zu diesem Zweck verfügt es über eineExcuse
Schnittstelle mit den Methoden generateExcuse()
, likeExcuse()
und dislikeExcuse()
.
public interface Excuse {
String generateExcuse();
void likeExcuse(String excuse);
void dislikeExcuse(String excuse);
}
Die WorkExcuse
Klasse implementiert diese Schnittstelle:
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
}
}
Testen wir unser Beispiel:
Excuse excuse = new WorkExcuse();
System.out.println(excuse.generateExcuse());
Ausgang:
"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.
Stellen Sie sich nun vor, Sie hätten einen Dienst zum Generieren von Ausreden gestartet, Statistiken gesammelt und festgestellt, dass die meisten Ihrer Benutzer Universitätsstudenten sind. Um diese Gruppe besser bedienen zu können, haben Sie einen anderen Entwickler gebeten, ein System zu entwickeln, das Ausreden speziell für Universitätsstudenten generiert. Das Entwicklungsteam führte Marktforschung durch, ordnete Ausreden ein, schaltete künstliche Intelligenz ein und integrierte den Dienst in Verkehrsmeldungen, Wetterberichte usw. Jetzt haben Sie eine Bibliothek zum Generieren von Ausreden für Universitätsstudenten, aber sie hat eine andere Schnittstelle: StudentExcuse
.
public interface StudentExcuse {
String generateExcuse();
void dislikeExcuse(String excuse);
}
Diese Schnittstelle verfügt über zwei Methoden: generateExcuse
, die eine Entschuldigung generiert, und dislikeExcuse
, die verhindert, dass die Entschuldigung in Zukunft erneut erscheint. Die Drittanbieter-Bibliothek kann nicht bearbeitet werden, d. h. Sie können ihren Quellcode nicht ändern. Was wir jetzt haben, ist ein System mit zwei Klassen, die die Excuse
Schnittstelle implementieren, und eine Bibliothek mit einer SuperStudentExcuse
Klasse, die die StudentExcuse
Schnittstelle implementiert:
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
}
}
Der Code kann nicht geändert werden. Die aktuelle Klassenhierarchie sieht folgendermaßen aus: ![Welche Probleme löst das Adapterentwurfsmuster? - 2](https://cdn.codegym.cc/images/article/5eb3ced7-e0e6-4dec-94a1-298d797d0e7d/800.jpeg)
![Welche Probleme löst das Adapterentwurfsmuster? - 3](https://cdn.codegym.cc/images/article/97a9295a-155b-4043-9c37-b187044a02df/800.jpeg)
Excuse
Schnittstelle umbenennen. Bei seriösen Anwendungen ist die zusätzliche Hierarchie jedoch unerwünscht: Die Einführung eines gemeinsamen Stammelements zerstört die Architektur. Sie sollten eine Zwischenklasse implementieren, die es uns ermöglicht, sowohl die neue als auch die alte Funktionalität mit minimalen Verlusten zu nutzen. Kurz gesagt, Sie benötigen einen Adapter .
Das Prinzip hinter dem Adaptermuster
Ein Adapter ist ein Zwischenobjekt, das es ermöglicht, dass die Methodenaufrufe eines Objekts von einem anderen verstanden werden. Lassen Sie uns für unser Beispiel einen Adapter implementieren und ihn nennenMiddleware
. Unser Adapter muss eine Schnittstelle implementieren, die mit einem der Objekte kompatibel ist. Lass es sein Excuse
. Dadurch können Middleware
die Methoden des ersten Objekts aufgerufen werden. Middleware
nimmt Anrufe entgegen und leitet sie kompatible an das zweite Objekt weiter. Hier ist die Middleware
Implementierung mit den Methoden generateExcuse
und 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
}
Testen (im Client-Code):
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
}
}
Ausgang:
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:
Eine unglaubliche Ausrede, angepasst an die aktuellen Wetterbedingungen, Staus oder Verspätungen im öffentlichen Nahverkehr. Die generateExcuse
Methode leitet den Aufruf einfach ohne weitere Änderungen an ein anderes Objekt weiter. Die dislikeExcuse
Methode erforderte, dass wir zunächst die Ausrede auf die schwarze Liste setzten. Die Fähigkeit, eine Zwischendatenverarbeitung durchzuführen, ist einer der Gründe, warum Menschen das Adaptermuster lieben. Aber was ist mit der likeExcuse
Methode, die Teil der Excuse
Schnittstelle, aber nicht Teil der StudentExcuse
Schnittstelle ist? Die neue Funktionalität unterstützt diesen Vorgang nicht. Der UnsupportedOperationException
wurde für diese Situation erfunden. Es wird ausgelöst, wenn der angeforderte Vorgang nicht unterstützt wird. Nutzen wir es. So Middleware
sieht die neue Implementierung der Klasse aus:
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.
}
}
Auf den ersten Blick scheint diese Lösung nicht sehr gut zu sein, aber die Nachahmung der Funktionalität kann die Situation verkomplizieren. Wenn der Kunde aufmerksam ist und der Adapter gut dokumentiert ist, ist eine solche Lösung akzeptabel.
Wann sollte ein Adapter verwendet werden?
-
Wenn Sie eine Drittanbieterklasse verwenden müssen, deren Schnittstelle jedoch nicht mit der Hauptanwendung kompatibel ist. Das obige Beispiel zeigt, wie ein Adapterobjekt erstellt wird, das Aufrufe in ein Format umschließt, das ein Zielobjekt verstehen kann.
-
Wenn mehrere vorhandene Unterklassen eine gemeinsame Funktionalität benötigen. Anstatt zusätzliche Unterklassen zu erstellen (was zu einer Duplizierung des Codes führt), 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 von 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. Wenn Sie viele inkompatible Schnittstellen haben, kann die Anzahl zusätzlicher Klassen unüberschaubar werden.Verwechseln Sie einen Adapter nicht mit einer Fassade oder einem Dekorateur
Bei nur oberflächlicher Betrachtung könnte ein Adapter mit den Fassaden- und Dekorationsmustern verwechselt werden. Der Unterschied zwischen einem Adapter und einer Fassade besteht darin, dass eine Fassade eine neue Schnittstelle einführt und das gesamte Subsystem umschließt. Und im Gegensatz zu einem Adapter verändert ein Dekorateur das Objekt selbst und nicht die Schnittstelle.Schritt-für-Schritt-Algorithmus
-
Stellen Sie zunächst sicher, dass Sie ein Problem haben, das dieses Muster lösen kann.
-
Definieren Sie die Client-Schnittstelle, die für die indirekte Interaktion mit inkompatiblen Objekten verwendet wird.
-
Lassen Sie die Adapterklasse die im vorherigen Schritt definierte Schnittstelle erben.
-
Erstellen Sie in der Adapterklasse ein Feld zum Speichern einer Referenz auf das Adaptee-Objekt. Diese Referenz wird an den Konstruktor übergeben.
-
Implementieren Sie alle Clientschnittstellenmethoden im Adapter. Eine Methode kann:
-
Leiten Sie Anrufe weiter, ohne Änderungen vorzunehmen
-
Ändern oder ergänzen Sie Daten, erhöhen/verringern Sie die Anzahl der Aufrufe der Zielmethode usw.
-
Wenn in extremen Fällen eine bestimmte Methode weiterhin inkompatibel ist, lösen Sie eine UnsupportedOperationException aus. Nicht unterstützte Vorgänge müssen streng dokumentiert werden.
-
-
Wenn die Anwendung die Adapterklasse nur über die Client-Schnittstelle verwendet (wie im obigen Beispiel), kann der Adapter in Zukunft problemlos erweitert werden.