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 adapterklasse, der konverterer (inkompatibel) grænseflade for en klasse ( adaptee) til en anden grænseflade ( target), som klienter kræver.
  • Arbejd igennem en adaptertil 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, adapterder tilpasser grænsefladen til en (allerede eksisterende) klasse uden at ændre den.

Kunder ved ikke, om de arbejder med en targetklasse direkte eller gennem en adaptermed en klasse, der ikke har targetgræ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

Et eksempel på et UML -klassediagram for adapterens designmønster.

I ovenstående UML -klassediagram kan den clientklasse, der kræver en targetgrænseflade, ikke genbruge adapteeklassen direkte, fordi dens grænseflade ikke er i overensstemmelse med targetgrænsefladen. I stedet clientfungerer gennem en adapterklasse, der implementerer targetgrænsefladen i form af adaptee:

  • Den object adaptermåde implementerer targetgrænsefladen ved at delegere til et adapteeobjekt på run-time ( adaptee.specificOperation()).
  • De class adaptermåde implementerer den targetinterfacet ved at arve fra en adapteeklasse 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 .

Objektadaptermønsteret udtrykt i UML

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.

Klasseadaptermønsteret udtrykt i UML .

En yderligere form for runtime -adaptermønster

Motivation fra kompileringstidsløsning

Det er ønsket classAat levere classBnogle data, lad os antage nogle Stringdata. 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:

  1. Definer en mellemliggende "udbyder" -grænseflade, og skriv en implementering af den udbydersgrænseflade, der omslutter datakilden, ClassAi 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();
        }
    }
    
  2. 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);
        }
    }
    
  3. Registrer adaptermed et globalt register, så der adapterkan slås op ved runtime:
    AdapterFactory.getInstance().registerAdapter(ClassA.class, ClassAFormat1Adapter.class, "format1");
    
  4. Når du ønsker at overføre data fra ClassAtil i kode, ClassBskal 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());
    
  5. 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");
    
  6. Og hvis det er ønsket at sende dataene fra ClassAsom 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());
    
  7. På denne måde tillader brugen af ​​adaptere og udbydere flere "visninger" af ClassBog ClassCtil ClassAuden 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]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));
    }
}

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å

Referencer