Design patterns
Adapter
Suche…
Adaptermuster (PHP)
Ein reales Beispiel mit einem wissenschaftlichen Experiment, bei dem bestimmte Routinen an verschiedenen Gewebearten durchgeführt werden. Die Klasse enthält standardmäßig zwei Funktionen, um das Gewebe oder die Routine separat abzurufen. In einer späteren Version haben wir es dann mithilfe einer neuen Klasse angepasst, um eine Funktion hinzuzufügen, die beides erhält. Dies bedeutet, dass wir den Originalcode nicht bearbeitet haben und daher kein Risiko besteht, dass die vorhandene Klasse beschädigt wird (und kein erneuter Test durchgeführt wird).
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().')';
}
}
Adapter (Java)
Nehmen wir an, in Ihrer aktuellen Codebase gibt es eine MyLogger
Schnittstelle wie MyLogger
:
interface MyLogger {
void logMessage(String message);
void logException(Throwable exception);
}
Nehmen wir an, Sie haben einige konkrete Implementierungen davon erstellt, wie MyFileLogger
und MyConsoleLogger
.
Sie haben sich entschieden, ein Framework zur Steuerung der Bluetooth-Verbindung Ihrer Anwendung zu verwenden. Dieses Framework enthält einen BluetoothManager
mit dem folgenden Konstruktor:
class BluetoothManager {
private FrameworkLogger logger;
public BluetoothManager(FrameworkLogger logger) {
this.logger = logger;
}
}
Der BluetoothManager
akzeptiert auch einen Logger, was großartig ist! Sie erwartet jedoch einen Logger, dessen Schnittstelle vom Framework definiert wurde, und sie haben Methodenüberladung verwendet, anstatt ihre Funktionen anders zu benennen:
interface FrameworkLogger {
void log(String message);
void log(Throwable exception);
}
Sie haben bereits eine Reihe von MyLogger
Implementierungen, die Sie wiederverwenden möchten, aber sie passen nicht in die Schnittstelle des FrameworkLogger
. Hier kommt das Adapterdesign-Muster ins Spiel:
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);
}
}
Durch die Definition einer Adapterklasse, die die FrameworkLogger
Schnittstelle implementiert und eine MyLogger
Implementierung akzeptiert, MyLogger
die Funktionalität zwischen den verschiedenen Schnittstellen zugeordnet werden. Jetzt ist es möglich, den BluetoothManager
mit allen MyLogger
Implementierungen wie MyLogger
:
FrameworkLogger fileLogger = new FrameworkLoggerAdapter(new MyFileLogger());
BluetoothManager manager = new BluetoothManager(fileLogger);
FrameworkLogger consoleLogger = new FrameworkLoggerAdapter(new MyConsoleLogger());
BluetoothManager manager2 = new BluetoothManager(consoleLogger);
Java-Beispiel
Ein hervorragendes Beispiel für das Adaptermuster finden Sie in den Klassen SWT MouseListener und MouseAdapter .
Die MouseListener-Oberfläche sieht folgendermaßen aus:
public interface MouseListener extends SWTEventListener {
public void mouseDoubleClick(MouseEvent e);
public void mouseDown(MouseEvent e);
public void mouseUp(MouseEvent e);
}
Stellen Sie sich nun ein Szenario vor, in dem Sie eine Benutzeroberfläche erstellen und diese Listener hinzufügen. Meistens kümmern Sie sich jedoch nicht um etwas anderes, als wenn ein einzelner Klick (mouseUp) ausgeführt wird. Sie möchten nicht ständig leere Implementierungen erstellen:
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
}
});
Stattdessen können wir MouseAdapter verwenden:
public abstract class MouseAdapter implements MouseListener {
public void mouseDoubleClick(MouseEvent e) { }
public void mouseDown(MouseEvent e) { }
public void mouseUp(MouseEvent e) { }
}
Durch die Bereitstellung von leeren Standardimplementierungen können wir nur die Methoden außer Kraft setzen, die uns vom Adapter aus wichtig sind. Aus dem obigen Beispiel folgend:
obj.addMouseListener(new MouseAdapter() {
@Override
public void mouseUp(MouseEvent e) {
// Do the things
}
});
Adapter (UML & Beispielsituation)
Um die Verwendung des Adaptermusters und die Art der Situation, in der es angewendet werden kann, denkbarer zu machen, wird hier ein kleines, einfaches und sehr konkretes Beispiel gegeben. Es gibt hier keinen Code, nur UML und eine Beschreibung der Beispielsituation und ihres Problems. Zugegebenermaßen ist der UML-Inhalt wie Java geschrieben. (Nun, im Hinweistext heißt es: "Gute Beispiele sind meist Code". Ich denke, Designmuster sind abstrakt genug, um auch auf andere Weise eingeführt zu werden.)
Im Allgemeinen ist das Adaptermuster eine angemessene Lösung für Situationen, in denen Sie nicht kompatible Schnittstellen haben und keine davon direkt überschrieben werden kann.
Stellen Sie sich vor, Sie betreiben einen schönen kleinen Pizzalieferservice. Kunden können online auf Ihrer Website bestellen, und Sie haben ein kleines System, das eine Pizza
Klasse verwendet, um Ihre Pizzen darzustellen und Rechnungen, Steuerberichte und mehr zu berechnen. Der Preis Ihrer Pizza wird als eine ganze Zahl angegeben, die den Preis in Cent (der Währung Ihrer Wahl) darstellt.
Ihr Lieferservice funktioniert prima, aber Sie können die wachsende Anzahl von Kunden irgendwann nicht mehr alleine bewältigen, möchten aber dennoch expandieren. Sie entscheiden sich dafür, Ihre Pizza in das Menü eines großen Online-Metadienstes aufzunehmen. Sie bieten viele verschiedene Mahlzeiten an - nicht nur Pizzen -, so dass ihr System die Abstraktion stärker nutzt und über ein Interface IMeal
das Mahlzeiten darstellt, die mit einer Klasse MoneyAmount
die Geld repräsentiert.
MoneyAmount
besteht aus zwei Ganzzahlen als Eingabe, eine für den Betrag (oder eine zufällige Währung) vor dem Komma und eine für den Cent-Betrag von 0 bis 99 nach dem Komma.
Aufgrund der Tatsache, dass der Preis Ihrer Pizza
eine einzelne Ganzzahl ist, die den Gesamtpreis als Cent (> 99) darstellt, ist sie nicht mit IMeal
kompatibel. Dies ist der Punkt, an dem das Adaptermuster ins Spiel kommt: Wenn es zu aufwendig wäre, das eigene System zu ändern oder ein neues zu erstellen, und Sie eine inkompatible Schnittstelle implementieren müssen, können Sie das Adaptermuster anwenden.
Das Muster kann auf zwei Arten angewendet werden: Klassenadapter und Objektadapter.
Beiden ist gemeinsam, dass ein Adapter ( PizzaAdapter
) als eine Art Übersetzer zwischen der neuen Schnittstelle und dem angepassten Benutzer (in diesem Beispiel Pizza
) fungiert. Der Adapter implementiert die neue Schnittstelle ( IMeal
) und erbt dann entweder von Pizza
und konvertiert seinen eigenen Preis von einer ganzen Zahl in zwei (Klassenadapter).
oder hat ein Objekt vom Typ Pizza
als Attribut und konvertiert dessen Werte (Objektadapter).
Durch Anwenden des Adaptermusters werden Sie gewissermaßen zwischen inkompatiblen Schnittstellen "übersetzen".