Adaptermuster - Adapter pattern
In der Softwareentwicklung ist das Adaptermuster ein Softwareentwurfsmuster (auch als Wrapper bekannt , eine alternative Benennung, die mit dem Dekoratormuster geteilt wird ), die es ermöglicht, die Schnittstelle einer vorhandenen Klasse als eine andere Schnittstelle zu verwenden. Es wird oft verwendet, um vorhandene Klassen mit anderen zusammenzuarbeiten, ohne deren Quellcode zu ändern .
Ein Beispiel ist ein Adapter, der die Schnittstelle eines Document Object Model eines XML- Dokuments in eine darstellbare Baumstruktur umwandelt .
Überblick
Das Adapter-Design-Pattern ist eines der dreiundzwanzig bekannten Gang-of-Four- Design-Patterns, die beschreiben, wie wiederkehrende Designprobleme gelöst werden, um flexible und wiederverwendbare objektorientierte Software zu entwerfen, d. h. Objekte, die einfacher zu implementieren, zu ändern, zu testen sind , und wiederverwenden.
Das Adapterdesignmuster löst Probleme wie:
- Wie kann eine Klasse wiederverwendet werden, die keine Schnittstelle hat, die ein Client benötigt?
- Wie können Klassen mit inkompatiblen Schnittstellen zusammenarbeiten?
- Wie kann eine alternative Schnittstelle für eine Klasse bereitgestellt werden?
Oft kann eine (bereits existierende) Klasse nur deshalb nicht wiederverwendet werden, weil ihre Schnittstelle nicht der von Clients geforderten Schnittstelle entspricht.
Das Adapter-Design-Pattern beschreibt, wie solche Probleme gelöst werden:
- Definieren Sie eine separate
adapterKlasse, die die (inkompatible) Schnittstelle einer Klasse (adaptee) in eine andere Schnittstelle (target) umwandelt, die Clients benötigen. - Arbeiten Sie durch
adapter, um mit (Wiederverwendungs-)Klassen zu arbeiten, die nicht über die erforderliche Schnittstelle verfügen.
Die Kernidee in diesem Muster ist, ein separates zu adapterdurcharbeiten, das die Schnittstelle einer (bereits vorhandenen) Klasse anpasst, ohne sie zu ändern.
Clients wissen nicht, ob sie mit einer targetKlasse direkt oder über eine adaptermit einer Klasse arbeiten, die die targetSchnittstelle nicht hat .
Siehe auch das UML-Klassendiagramm unten.
Definition
Ein Adapter ermöglicht die Zusammenarbeit zweier inkompatibler Schnittstellen. Dies ist die reale Definition für einen Adapter. Schnittstellen können inkompatibel sein, aber die innere Funktionalität sollte dem Bedarf entsprechen. Das Adapterentwurfsmuster ermöglicht die Zusammenarbeit ansonsten inkompatibler Klassen, indem die Schnittstelle einer Klasse in eine von den Clients erwartete Schnittstelle umgewandelt wird.
Verwendungszweck
Ein Adapter kann verwendet werden, wenn der Wrapper eine bestimmte Schnittstelle respektieren und polymorphes Verhalten unterstützen muss . Alternativ ermöglicht es ein Dekorator, das Verhalten einer Schnittstelle zur Laufzeit hinzuzufügen oder zu ändern, und eine Fassade wird verwendet, wenn eine einfachere oder einfachere Schnittstelle zu einem darunterliegenden Objekt gewünscht wird.
| Muster | Absicht |
|---|---|
| Adapter oder Wrapper | Konvertiert eine Schnittstelle in eine andere, sodass sie den Erwartungen des Clients entspricht |
| Dekorateur | Fügt der Schnittstelle dynamisch Verantwortung hinzu, indem der Originalcode verpackt wird |
| Delegation | Unterstützen Sie "Komposition über Vererbung" |
| Fassade | Bietet eine vereinfachte Schnittstelle |
Struktur
UML-Klassendiagramm
Im obigen UML -Klassendiagramm kann die clientKlasse, die eine targetSchnittstelle erfordert , die Klasse nicht adapteedirekt wiederverwenden, da ihre Schnittstelle nicht der targetSchnittstelle entspricht. Stattdessen arbeitet das clientüber eine adapterKlasse, die die targetSchnittstelle in Bezug auf Folgendes implementiert adaptee:
- Der
object adapterWeg implementiert dietargetSchnittstelle, indem er zuradapteeLaufzeit an ein Objekt delegiert (adaptee.specificOperation()). - Der
class adapterWeg implementiert dietargetSchnittstelle, indem eradapteezur Kompilierzeit (specificOperation()) von einer Klasse erbt .
Objektadaptermuster
In diesem Adaptermuster enthält der Adapter eine Instanz der Klasse, die er umschließt. In dieser Situation ruft der Adapter die Instanz des umschlossenen Objekts auf .
Klassenadaptermuster
Dieses Adaptermuster verwendet mehrere polymorphe Schnittstellen , die sowohl die erwartete als auch die bereits vorhandene Schnittstelle implementieren oder erben. Es ist typisch, dass die erwartete Schnittstelle als reine Schnittstellenklasse erstellt wird , insbesondere in Sprachen wie Java (vor JDK 1.8), die die Mehrfachvererbung von Klassen nicht unterstützen .
Eine weitere Form von Laufzeitadapter-Pattern
Motivation aus Kompilierzeitlösung
Es ist erwünscht classA, classBeinige Daten zu liefern , nehmen wir an, einige StringDaten. Eine Lösung zur Kompilierungszeit ist:
classB.setStringData(classA.getStringData());
Nehmen Sie jedoch an, dass das Format der Zeichenfolgendaten geändert werden muss. Eine Lösung zur Kompilierungszeit besteht darin, Vererbung zu verwenden:
public class Format1ClassA extends ClassA {
@Override
public String getStringData() {
return format(toString());
}
}
und vielleicht zur Laufzeit das korrekt "formatierende" Objekt mit Hilfe des Factory-Patterns erstellen .
Laufzeitadapterlösung
Eine Lösung mit "Adaptern" geht wie folgt vor:
- Definieren Sie eine zwischengeschaltete "Provider"-Schnittstelle und schreiben Sie eine Implementierung dieser Provider-Schnittstelle, die
ClassAin diesem Beispiel die Quelle der Daten umschließt und die Daten entsprechend formatiert ausgibt:public interface StringProvider { public String getStringData(); } public class ClassAFormat1 implements StringProvider { private ClassA classA = null; public ClassAFormat1(final ClassA a) { classA = a; } public String getStringData() { return format(classA.getStringData()); } private String format(final String sourceValue) { // Manipulate the source string into a format required // by the object needing the source object's data return sourceValue.trim(); } }
- Schreiben Sie eine Adapterklasse, die die spezifische Implementierung des Anbieters zurückgibt:
public class ClassAFormat1Adapter extends Adapter { public Object adapt(final Object anObject) { return new ClassAFormat1((ClassA) anObject); } }
- Registrieren Sie das
adapterbei einer globalen Registry, damit das zuradapterLaufzeit nachgeschlagen werden kann:AdapterFactory.getInstance().registerAdapter(ClassA.class, ClassAFormat1Adapter.class, "format1");
- Schreiben Sie im Code, wenn Sie Daten von
ClassAnach übertragen möchtenClassB:Adapter adapter = AdapterFactory.getInstance() .getAdapterFromTo(ClassA.class, StringProvider.class, "format1"); StringProvider provider = (StringProvider) adapter.adapt(classA); String string = provider.getStringData(); classB.setStringData(string);
oder kurz gefasst:
classB.setStringData( ((StringProvider) AdapterFactory.getInstance() .getAdapterFromTo(ClassA.class, StringProvider.class, "format1") .adapt(classA)) .getStringData());
- Der Vorteil ist darin zu sehen, dass, wenn die Daten in einem zweiten Format übertragen werden sollen, der andere Adapter/Provider nachgeschaut werden muss:
Adapter adapter = AdapterFactory.getInstance() .getAdapterFromTo(ClassA.class, StringProvider.class, "format2");
- Und wenn Sie die Daten beispielsweise
ClassAals Bilddaten in ausgeben möchten :Class CAdapter adapter = AdapterFactory.getInstance() .getAdapterFromTo(ClassA.class, ImageProvider.class, "format2"); ImageProvider provider = (ImageProvider) adapter.adapt(classA); classC.setImage(provider.getImage());
- Auf diese Weise ermöglicht die Verwendung von Adaptern und Providern mehrere "Ansichten" von
ClassBundClassCin,ClassAohne die Klassenhierarchie ändern zu müssen. Im Allgemeinen ermöglicht es einen Mechanismus für beliebige Datenflüsse zwischen Objekten, der in eine bestehende Objekthierarchie nachgerüstet werden kann.
Umsetzung des Adaptermusters
Bei der Implementierung des Adaptermusters kann man der Übersichtlichkeit halber den Klassennamen auf die Provider-Implementierung anwenden ; zum Beispiel, . Es sollte eine Konstruktormethode mit einer angepassten Klassenvariablen als Parameter haben. Dieser Parameter wird an ein Instanzmitglied von übergeben . Wenn clientMethod aufgerufen wird, hat sie Zugriff auf die Instanz des Adaptierten, die den Zugriff auf die erforderlichen Daten des Adaptees und das Ausführen von Operationen an diesen Daten ermöglicht, die die gewünschte Ausgabe erzeugen.
[ClassName]To[Interface]AdapterDAOToProviderAdapter[ClassName]To[Interface]Adapter
Java
interface LightningPhone {
void recharge();
void useLightning();
}
interface MicroUsbPhone {
void recharge();
void useMicroUsb();
}
class Iphone implements LightningPhone {
private boolean connector;
@Override
public void useLightning() {
connector = true;
System.out.println("Lightning connected");
}
@Override
public void recharge() {
if (connector) {
System.out.println("Recharge started");
System.out.println("Recharge finished");
} else {
System.out.println("Connect Lightning first");
}
}
}
class Android implements MicroUsbPhone {
private boolean connector;
@Override
public void useMicroUsb() {
connector = true;
System.out.println("MicroUsb connected");
}
@Override
public void recharge() {
if (connector) {
System.out.println("Recharge started");
System.out.println("Recharge finished");
} else {
System.out.println("Connect MicroUsb first");
}
}
}
/* exposing the target interface while wrapping source object */
class LightningToMicroUsbAdapter implements MicroUsbPhone {
private final LightningPhone lightningPhone;
public LightningToMicroUsbAdapter(LightningPhone lightningPhone) {
this.lightningPhone = lightningPhone;
}
@Override
public void useMicroUsb() {
System.out.println("MicroUsb connected");
lightningPhone.useLightning();
}
@Override
public void recharge() {
lightningPhone.recharge();
}
}
public class AdapterDemo {
static void rechargeMicroUsbPhone(MicroUsbPhone phone) {
phone.useMicroUsb();
phone.recharge();
}
static void rechargeLightningPhone(LightningPhone phone) {
phone.useLightning();
phone.recharge();
}
public static void main(String[] args) {
Android android = new Android();
Iphone iPhone = new Iphone();
System.out.println("Recharging android with MicroUsb");
rechargeMicroUsbPhone(android);
System.out.println("Recharging iPhone with Lightning");
rechargeLightningPhone(iPhone);
System.out.println("Recharging iPhone with MicroUsb");
rechargeMicroUsbPhone(new LightningToMicroUsbAdapter (iPhone));
}
}
Ausgabe
Recharging android with MicroUsb MicroUsb connected Recharge started Recharge finished Recharging iPhone with Lightning Lightning connected Recharge started Recharge finished Recharging iPhone with MicroUsb MicroUsb connected Lightning connected Recharge started Recharge finished
Python
"""
Adapter pattern example.
"""
from abc import ABCMeta, abstractmethod
NOT_IMPLEMENTED = "You should implement this."
RECHARGE = ["Recharge started.", "Recharge finished."]
POWER_ADAPTERS = {"Android": "MicroUSB", "iPhone": "Lightning"}
CONNECTED = "{} connected."
CONNECT_FIRST = "Connect {} first."
class RechargeTemplate:
__metaclass__ = ABCMeta
@abstractmethod
def recharge(self):
raise NotImplementedError(NOT_IMPLEMENTED)
class FormatIPhone(RechargeTemplate):
@abstractmethod
def use_lightning(self):
raise NotImplementedError(NOT_IMPLEMENTED)
class FormatAndroid(RechargeTemplate):
@abstractmethod
def use_micro_usb(self):
raise NotImplementedError(NOT_IMPLEMENTED)
class IPhone(FormatIPhone):
__name__ = "iPhone"
def __init__(self):
self.connector = False
def use_lightning(self):
self.connector = True
print(CONNECTED.format(POWER_ADAPTERS[self.__name__]))
def recharge(self):
if self.connector:
for state in RECHARGE:
print(state)
else:
print(CONNECT_FIRST.format(POWER_ADAPTERS[self.__name__]))
class Android(FormatAndroid):
__name__ = "Android"
def __init__(self):
self.connector = False
def use_micro_usb(self):
self.connector = True
print(CONNECTED.format(POWER_ADAPTERS[self.__name__]))
def recharge(self):
if self.connector:
for state in RECHARGE:
print(state)
else:
print(CONNECT_FIRST.format(POWER_ADAPTERS[self.__name__]))
class IPhoneAdapter(FormatAndroid):
def __init__(self, mobile):
self.mobile = mobile
def recharge(self):
self.mobile.recharge()
def use_micro_usb(self):
print(CONNECTED.format(POWER_ADAPTERS["Android"]))
self.mobile.use_lightning()
class AndroidRecharger:
def __init__(self):
self.phone = Android()
self.phone.use_micro_usb()
self.phone.recharge()
class IPhoneMicroUSBRecharger:
def __init__(self):
self.phone = IPhone()
self.phone_adapter = IPhoneAdapter(self.phone)
self.phone_adapter.use_micro_usb()
self.phone_adapter.recharge()
class IPhoneRecharger:
def __init__(self):
self.phone = IPhone()
self.phone.use_lightning()
self.phone.recharge()
print("Recharging Android with MicroUSB recharger.")
AndroidRecharger()
print()
print("Recharging iPhone with MicroUSB using adapter pattern.")
IPhoneMicroUSBRecharger()
print()
print("Recharging iPhone with iPhone recharger.")
IPhoneRecharger()
C#
public interface ILightningPhone
{
void ConnectLightning();
void Recharge();
}
public interface IUsbPhone
{
void ConnectUsb();
void Recharge();
}
public sealed class AndroidPhone : IUsbPhone
{
private bool isConnected;
public void ConnectUsb()
{
this.isConnected = true;
Console.WriteLine("Android phone connected.");
}
public void Recharge()
{
if (this.isConnected)
{
Console.WriteLine("Android phone recharging.");
}
else
{
Console.WriteLine("Connect the USB cable first.");
}
}
}
public sealed class ApplePhone : ILightningPhone
{
private bool isConnected;
public void ConnectLightning()
{
this.isConnected = true;
Console.WriteLine("Apple phone connected.");
}
public void Recharge()
{
if (this.isConnected)
{
Console.WriteLine("Apple phone recharging.");
}
else
{
Console.WriteLine("Connect the Lightning cable first.");
}
}
}
public sealed class LightningToUsbAdapter : IUsbPhone
{
private readonly ILightningPhone lightningPhone;
private bool isConnected;
public LightningToUsbAdapter(ILightningPhone lightningPhone)
{
this.lightningPhone = lightningPhone;
this.lightningPhone.ConnectLightning();
}
public void ConnectUsb()
{
this.isConnected = true;
Console.WriteLine("Adapter cable connected.");
}
public void Recharge()
{
if (this.isConnected)
{
this.lightningPhone.Recharge();
}
else
{
Console.WriteLine("Connect the USB cable first.");
}
}
}
public void Main()
{
ILightningPhone applePhone = new ApplePhone();
IUsbPhone adapterCable = new LightningToUsbAdapter(applePhone);
adapterCable.ConnectUsb();
adapterCable.Recharge();
}
Ausgabe:
Apple phone connected.
Adapter cable connected.
Apple phone recharging.
Siehe auch
- Adapter Java Design Patterns - Adapter
- Delegation , stark relevant für das Objektadaptermuster.
- Abhängigkeitsinversionsprinzip , das man sich als Anwendung des Adaptermusters vorstellen kann, wenn die High-Level-Klasse ihre eigene (Adapter-)Schnittstelle zum Low-Level-Modul definiert (implementiert durch eine adaptierte Klasse).
- Ports und Adapterarchitektur
- Shim
- Wrapper-Funktion
- Wrapper-Bibliothek
