Vzor adaptéru - Adapter pattern
V softwarové inženýrství je adaptér vzor je software návrhový vzor (známý také jako obal , alternativní pojmenování sdílené s dekorátor ), který umožňuje, aby rozhraní existující třídy , které mají být použity jako další rozhraní. Často se používá k tomu, aby existující třídy fungovaly s ostatními bez úpravy jejich zdrojového kódu .
Příkladem je adaptér, který převádí rozhraní z Document Object Model of o XML dokumentu do stromové struktury, které mohou být zobrazeny.
Přehled
Návrhový vzor adaptéru je jedním z dvaceti tří známých návrhových vzorů Gang of Four, které popisují, jak řešit opakující se konstrukční problémy pro návrh flexibilního a opakovaně použitelného objektově orientovaného softwaru, tj. Objektů, které lze snadněji implementovat, měnit, testovat a znovu použít.
Návrhový vzor adaptéru řeší problémy jako:
- Jak lze znovu použít třídu, která nemá rozhraní, které vyžaduje klient?
- Jak mohou třídy, které mají nekompatibilní rozhraní, spolupracovat?
- Jak lze pro třídu poskytnout alternativní rozhraní?
Třídu (již existující) nelze často znovu použít pouze proto, že její rozhraní neodpovídá požadavkům, které klienti vyžadují.
Návrhový vzor adaptéru popisuje, jak takové problémy vyřešit:
- Definujte samostatnou
adapter
třídu, která převádí (nekompatibilní) rozhraní třídy (adaptee
) na jinétarget
klienty rozhraní ( ), které vyžadují. - Projděte si třídy
adapter
pro práci s (opětovným použitím) tříd, které nemají požadované rozhraní.
Klíčovou myšlenkou v tomto vzoru je projít oddělením, adapter
které přizpůsobí rozhraní (již existující) třídy, aniž by jej změnilo.
Klienti nevědí, zda pracují s target
třídou přímo, nebo prostřednictvím adapter
třídy, která toto target
rozhraní nemá .
Viz také diagram tříd UML níže.
Definice
Adaptér umožňuje spolupráci dvou nekompatibilních rozhraní. Toto je skutečná definice adaptéru. Rozhraní mohou být nekompatibilní, ale vnitřní funkce by měla odpovídat potřebám. Návrhový vzor adaptéru umožňuje jinak nekompatibilním třídám spolupracovat převedením rozhraní jedné třídy na rozhraní očekávané klienty.
Používání
Adaptér lze použít, když musí modul wrapper respektovat konkrétní rozhraní a musí podporovat polymorfní chování. Alternativně dekorátor umožňuje přidat nebo změnit chování rozhraní za běhu a fasáda se používá, když je požadováno jednodušší nebo jednodušší rozhraní k podkladovému objektu.
Vzor | Úmysl |
---|---|
Adaptér nebo obal | Převede jedno rozhraní na druhé tak, aby odpovídalo očekávání klienta |
Dekoratér | Dynamicky přidává odpovědnost rozhraní zabalením původního kódu |
Delegace | Podporujte „kompozici nad dědičností“ |
Fasáda | Poskytuje zjednodušené rozhraní |
Struktura
Diagram tříd UML
Ve výše uvedeném diagramu tříd UML třída , client
která vyžaduje target
rozhraní, nemůže znovu použít adaptee
třídu přímo, protože její rozhraní nevyhovuje target
rozhraní. Místo toho client
funguje prostřednictvím adapter
třídy, která implementuje target
rozhraní z hlediska adaptee
:
object adapter
Způsob implementujetarget
rozhraní tím, že přenese doadaptee
objektu za běhu (adaptee.specificOperation()
).- Na
class adapter
způsob implementujetarget
rozhraní podle dědí zadaptee
třídy na kompilaci (specificOperation()
).
Vzor adaptéru objektu
V tomto vzoru adaptéru obsahuje adaptér instanci třídy, kterou zabalí. V této situaci adaptér provádí volání instance zabaleného objektu .
Vzor adaptéru třídy
Tento vzor adaptéru používá více polymorfních rozhraní, která implementují nebo dědí jak očekávané rozhraní, tak rozhraní, které již existuje. Je typické, že očekávané rozhraní bude vytvořeno jako čistá třída rozhraní , zejména v jazycích, jako je Java (před JDK 1.8), které nepodporují vícenásobné dědění tříd.
Další forma vzorce běhového adaptéru
Motivace z časového řešení kompilace
Je žádoucí classA
dodat classB
nějaké údaje, předpokládejme nějaké String
. Řešení kompilace je:
classB.setStringData(classA.getStringData());
Předpokládejme však, že formát řetězcových dat musí být různý. Časovým kompilačním řešením je použít dědičnost:
public class Format1ClassA extends ClassA {
@Override
public String getStringData() {
return format(toString());
}
}
a možná vytvořte správně "formátující" objekt za běhu pomocí továrního vzoru .
Řešení adaptéru za běhu
Řešení využívající „adaptéry“ probíhá následovně:
ClassA
V tomto příkladu definujte zprostředkující rozhraní „poskytovatele“ a napište implementaci tohoto rozhraní poskytovatele, která zabalí zdroj dat , a podle potřeby naformátuje data: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(); } }
- Napište třídu adaptéru, která vrátí konkrétní implementaci poskytovatele:
public class ClassAFormat1Adapter extends Adapter { public Object adapt(final Object anObject) { return new ClassAFormat1((ClassA) anObject); } }
- Zaregistrujte
adapter
se v globálním registru, aby byloadapter
možné vyhledat za běhu:AdapterFactory.getInstance().registerAdapter(ClassA.class, ClassAFormat1Adapter.class, "format1");
- V kódu, pokud si přejete přenášet data z
ClassA
doClassB
, napište:Adapter adapter = AdapterFactory.getInstance() .getAdapterFromTo(ClassA.class, StringProvider.class, "format1"); StringProvider provider = (StringProvider) adapter.adapt(classA); String string = provider.getStringData(); classB.setStringData(string);
nebo stručněji:
classB.setStringData( ((StringProvider) AdapterFactory.getInstance() .getAdapterFromTo(ClassA.class, StringProvider.class, "format1") .adapt(classA)) .getStringData());
- Výhodu lze vidět v tom, že pokud je žádoucí přenášet data ve druhém formátu, vyhledejte jiný adaptér / poskytovatele:
Adapter adapter = AdapterFactory.getInstance() .getAdapterFromTo(ClassA.class, StringProvider.class, "format2");
- A pokud je požadováno výstup dat z
ClassA
, řekněme, obrazových dat v :Class C
Adapter adapter = AdapterFactory.getInstance() .getAdapterFromTo(ClassA.class, ImageProvider.class, "format2"); ImageProvider provider = (ImageProvider) adapter.adapt(classA); classC.setImage(provider.getImage());
- Tímto způsobem použití adaptérů a zprostředkovatelů umožňuje více „zobrazení“ dovnitř
ClassB
aClassC
do,ClassA
aniž by bylo nutné měnit hierarchii tříd. Obecně to umožňuje mechanismus pro libovolné datové toky mezi objekty, které lze dovybavit existující hierarchií objektů.
Implementace vzoru adaptéru
Při implementaci vzoru adaptéru lze pro přehlednost použít název třídy na implementaci poskytovatele; například . Měla by mít metodu konstruktoru s proměnnou třídy adaptability jako parametr. Tento parametr bude předán členu instance . Když se volá clientMethod, bude mít přístup k instanci adaptátu, která umožňuje přístup k požadovaným datům adaptéru a provádění operací s těmito daty, která generují požadovaný výstup.
[ClassName]To[Interface]Adapter
DAOToProviderAdapter
[ClassName]To[Interface]Adapter
Jáva
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));
}
}
Výstup
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
Krajta
"""
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();
}
Výstup:
Apple phone connected.
Adapter cable connected.
Apple phone recharging.
Viz také
- Adaptér Java Design Patterns - Adaptér
- Delegace , silně relevantní pro vzor adaptéru objektu.
- Princip inverze závislostí , který lze považovat za použití vzoru adaptéru, když třída na vysoké úrovni definuje vlastní (adaptér) rozhraní pro modul na nízké úrovni (implementováno třídou adaptee).
- Architektura portů a adaptérů
- Shim
- Funkce obálky
- Knihovna obalů