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 adaptertřídu, která převádí (nekompatibilní) rozhraní třídy ( adaptee) na jiné targetklienty rozhraní ( ), které vyžadují.
  • Projděte si třídy adapterpro 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, adapterkteré přizpůsobí rozhraní (již existující) třídy, aniž by jej změnilo.

Klienti nevědí, zda pracují s targettřídou přímo, nebo prostřednictvím adaptertřídy, která toto targetrozhraní 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

Ukázkový diagram tříd UML pro návrhový vzor adaptéru.

Ve výše uvedeném diagramu tříd UML třída , clientkterá vyžaduje targetrozhraní, nemůže znovu použít adapteetřídu přímo, protože její rozhraní nevyhovuje targetrozhraní. Místo toho clientfunguje prostřednictvím adaptertřídy, která implementuje targetrozhraní z hlediska adaptee:

  • object adapterZpůsob implementuje targetrozhraní tím, že přenese do adapteeobjektu za běhu ( adaptee.specificOperation()).
  • Na class adapterzpůsob implementuje targetrozhraní podle dědí z adapteetří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 objektu vyjádřený v UML
Vzor adaptéru objektu vyjádřený v LePUS3

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.

Vzor adaptéru třídy vyjádřený v UML .
Vzor adaptéru třídy vyjádřený v LePUS3

Další forma vzorce běhového adaptéru

Motivace z časového řešení kompilace

Je žádoucí classAdodat classBně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ě:

  1. ClassAV 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();
        }
    }
    
  2. 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);
        }
    }
    
  3. Zaregistrujte adapterse v globálním registru, aby bylo adaptermožné vyhledat za běhu:
    AdapterFactory.getInstance().registerAdapter(ClassA.class, ClassAFormat1Adapter.class, "format1");
    
  4. V kódu, pokud si přejete přenášet data z ClassAdo ClassB, 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());
    
  5. 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");
    
  6. 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());
    
  7. Tímto způsobem použití adaptérů a zprostředkovatelů umožňuje více „zobrazení“ dovnitř ClassBa ClassCdo, ClassAaniž 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]AdapterDAOToProviderAdapter[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é

Reference