Adaptermønster - Adapter pattern
I softwareteknik er adaptermønsteret et softwaredesignmønster (også kendt som wrapper , en alternativ navngivning, der deles med dekoratormønsteret ), der gør det muligt at bruge grænsefladen til en eksisterende klasse som en anden grænseflade. Det bruges ofte til at få eksisterende klasser til at arbejde sammen med andre uden at ændre deres kildekode .
Et eksempel er en adapter, der konverterer grænsefladen for en dokumentobjektmodel for et XML -dokument til en træstruktur, der kan vises.
Oversigt
Adapterdesignmønsteret er et af de tre velkendte Gang of Four- designmønstre, der beskriver, hvordan man løser tilbagevendende designproblemer til at designe fleksibel og genanvendelig objektorienteret software, det vil sige objekter, der er lettere at implementere, ændre, teste , og genbruge.
Adapterens designmønster løser problemer som:
- Hvordan kan en klasse genbruges, der ikke har en grænseflade, som en klient kræver?
- Hvordan kan klasser, der har inkompatible grænseflader, fungere sammen?
- Hvordan kan en alternativ grænseflade tilvejebringes til en klasse?
Ofte kan en (allerede eksisterende) klasse ikke genbruges, kun fordi dens grænseflade ikke er i overensstemmelse med den grænseflade, som klienter kræver.
Adapterens designmønster beskriver, hvordan man løser sådanne problemer:
- Definer en separat
adapter
klasse, der konverterer (inkompatibel) grænseflade for en klasse (adaptee
) til en anden grænseflade (target
), som klienter kræver. - Arbejd igennem en
adapter
til at arbejde med (genbrug) klasser, der ikke har den nødvendige grænseflade.
Nøgleidéen i dette mønster er at arbejde igennem en separat, adapter
der tilpasser grænsefladen til en (allerede eksisterende) klasse uden at ændre den.
Kunder ved ikke, om de arbejder med en target
klasse direkte eller gennem en adapter
med en klasse, der ikke har target
grænsefladen.
Se også UML -klassediagrammet herunder.
Definition
En adapter tillader to inkompatible grænseflader at arbejde sammen. Dette er den virkelige definition af en adapter. Grænseflader kan være inkompatible, men den indre funktionalitet bør passe til behovet. Adapterdesignmønsteret tillader ellers inkompatible klasser at arbejde sammen ved at konvertere grænsefladen for en klasse til en grænseflade, der forventes af klienterne.
Anvendelse
En adapter kan bruges, når indpakningen skal respektere en bestemt grænseflade og skal understøtte polymorf adfærd. Alternativt gør en dekoratør det muligt at tilføje eller ændre adfærd for en grænseflade i løbetid, og en facade bruges, når der ønskes en lettere eller enklere grænseflade til et underliggende objekt.
Mønster | Hensigt |
---|---|
Adapter eller indpakning | Konverterer en grænseflade til en anden, så den matcher, hvad klienten forventer |
Dekoratør | Dynamisk tilføjer ansvar til grænsefladen ved at indpakke den originale kode |
Delegation | Støtte "sammensætning over arv" |
Facade | Giver en forenklet grænseflade |
Struktur
UML klassediagram
I ovenstående UML -klassediagram kan den client
klasse, der kræver en target
grænseflade, ikke genbruge adaptee
klassen direkte, fordi dens grænseflade ikke er i overensstemmelse med target
grænsefladen. I stedet client
fungerer gennem en adapter
klasse, der implementerer target
grænsefladen i form af adaptee
:
- Den
object adapter
måde implementerertarget
grænsefladen ved at delegere til etadaptee
objekt på run-time (adaptee.specificOperation()
). - De
class adapter
måde implementerer dentarget
interfacet ved at arve fra enadaptee
klasse på kompileringstidspunktet (specificOperation()
).
Objektadaptermønster
I dette adaptermønster indeholder adapteren en forekomst af den klasse, den omslutter. I denne situation foretager adapteren opkald til instansen af det indpakkede objekt .
Klasse adapter mønster
Dette adaptermønster bruger flere polymorfe grænseflader, der implementerer eller arver både den forventede grænseflade og den eksisterende grænseflade. Det er typisk, at den forventede grænseflade oprettes som en ren grænsefladeklasse , især på sprog som Java (før JDK 1.8), der ikke understøtter flere arv af klasser.
En yderligere form for runtime -adaptermønster
Motivation fra kompileringstidsløsning
Det er ønsket classA
at levere classB
nogle data, lad os antage nogle String
data. En kompileringstidsløsning er:
classB.setStringData(classA.getStringData());
Antag dog, at formatet på strengdataene skal varieres. En kompileringstidsløsning er at bruge arv:
public class Format1ClassA extends ClassA {
@Override
public String getStringData() {
return format(toString());
}
}
og måske oprette det rigtige "formaterings" -objekt ved kørsel ved hjælp af fabriksmønsteret .
Run-time adapter løsning
En løsning ved hjælp af "adaptere" forløber som følger:
- Definer en mellemliggende "udbyder" -grænseflade, og skriv en implementering af den udbydersgrænseflade, der omslutter datakilden,
ClassA
i dette eksempel, og output dataene formateret efter behov: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(); } }
- Skriv en adapterklasse, der returnerer den specifikke implementering af udbyderen:
public class ClassAFormat1Adapter extends Adapter { public Object adapt(final Object anObject) { return new ClassAFormat1((ClassA) anObject); } }
- Registrer
adapter
med et globalt register, så deradapter
kan slås op ved runtime:AdapterFactory.getInstance().registerAdapter(ClassA.class, ClassAFormat1Adapter.class, "format1");
- Når du ønsker at overføre data fra
ClassA
til i kode,ClassB
skal du skrive:Adapter adapter = AdapterFactory.getInstance() .getAdapterFromTo(ClassA.class, StringProvider.class, "format1"); StringProvider provider = (StringProvider) adapter.adapt(classA); String string = provider.getStringData(); classB.setStringData(string);
eller mere præcist:
classB.setStringData( ((StringProvider) AdapterFactory.getInstance() .getAdapterFromTo(ClassA.class, StringProvider.class, "format1") .adapt(classA)) .getStringData());
- Fordelen kan ses ved, at hvis det ønskes at overføre dataene i et andet format, så slå den forskellige adapter/udbyder op:
Adapter adapter = AdapterFactory.getInstance() .getAdapterFromTo(ClassA.class, StringProvider.class, "format2");
- Og hvis det er ønsket at sende dataene fra
ClassA
som f.eks. Billeddata i :Class C
Adapter adapter = AdapterFactory.getInstance() .getAdapterFromTo(ClassA.class, ImageProvider.class, "format2"); ImageProvider provider = (ImageProvider) adapter.adapt(classA); classC.setImage(provider.getImage());
- På denne måde tillader brugen af adaptere og udbydere flere "visninger" af
ClassB
ogClassC
tilClassA
uden at skulle ændre klassehierarkiet. Generelt tillader det en mekanisme til vilkårlige datastrømme mellem objekter, der kan eftermonteres i et eksisterende objekthierarki.
Implementering af adaptermønsteret
Ved implementering af adaptermønsteret kan man for klarhedens skyld anvende klassens navn på implementering af udbyderen; for eksempel ,. Den skal have en konstruktormetode med en adaptiv klassevariabel som parameter. Denne parameter videregives til et forekomstmedlem af . Når clientMethod kaldes, vil den have adgang til den adapterede forekomst, der giver adgang til den påkrævede data for den adapterede og udføre operationer på de data, der genererer det ønskede output.
[ClassName]To[Interface]Adapter
DAOToProviderAdapter
[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));
}
}
Produktion
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();
}
Produktion:
Apple phone connected.
Adapter cable connected.
Apple phone recharging.
Se også
- Adapter Java Design Patterns - Adapter
- Delegation , stærkt relevant for objektadaptermønsteret.
- Afhængighedsinversionsprincip , som kan betragtes som at anvende adaptermønsteret, når klassen på højt niveau definerer sin egen (adapter) grænseflade til modulet på lavt niveau (implementeret af en adapterklasse).
- Porte og adaptere arkitektur
- Shim
- Indpakningsfunktion
- Indpakningsbibliotek