Wzór adaptera - Adapter pattern

W inżynierii oprogramowania , wzór adapter jest wzorzec projektowania oprogramowania (znany również jako owijki , alternatywnego nazewnictwa wspólnego ze wzoru dekorator ), który umożliwia interfejs istniejącej klasy do stosowania jako inny interfejs. Jest często używany, aby istniejące klasy współpracowały z innymi bez modyfikowania ich kodu źródłowego .

Przykładem jest adapter, który konwertuje interfejsu z Document Object Model wystąpienia XML dokumentu w strukturze drzewa, które mogą być wyświetlane.

Przegląd

Wzorzec projektowy adaptera jest jednym z dwudziestu trzech dobrze znanych wzorców projektowych Gang of Four opisujących sposób rozwiązywania powtarzających się problemów projektowych w celu projektowania elastycznego i wielokrotnego użytku oprogramowania zorientowanego obiektowo, czyli obiektów, które są łatwiejsze do wdrożenia, zmiany, testowania i ponownie użyj.

Wzorzec projektowy adaptera rozwiązuje takie problemy jak:

  • Jak można ponownie wykorzystać klasę, która nie ma interfejsu wymaganego przez klienta?
  • W jaki sposób klasy, które mają niezgodne interfejsy, mogą ze sobą współpracować?
  • Jak zapewnić alternatywny interfejs dla klasy?

Często (już istniejącej) klasy nie można ponownie użyć tylko dlatego, że jej interfejs nie jest zgodny z wymaganiami klientów.

Wzorzec projektowy adaptera opisuje, jak rozwiązać takie problemy:

  • Zdefiniuj oddzielną adapterklasę, która konwertuje (niezgodny) interfejs klasy ( adaptee) na inny interfejs ( target) wymagany przez klientów.
  • Praca przez an adapterdo pracy z (ponownie) klasami, które nie mają wymaganego interfejsu.

Kluczową ideą tego wzorca jest praca przez oddzielny, adapterktóry dostosowuje interfejs (już istniejącej) klasy bez jego zmiany.

Klienci nie wiedzą, czy pracują z targetklasą bezpośrednio, czy poprzez adapterklasę, która nie ma targetinterfejsu.

Zobacz także diagram klas UML poniżej.

Definicja

Adapter umożliwia współpracę dwóch niekompatybilnych interfejsów. To jest rzeczywista definicja adaptera. Interfejsy mogą być niekompatybilne, ale wewnętrzna funkcjonalność powinna odpowiadać potrzebom. Wzorzec projektu adaptera umożliwia współpracę klas, które w przeciwnym razie nie są zgodne, poprzez przekształcenie interfejsu jednej klasy w interfejs oczekiwany przez klientów.

Stosowanie

Adapter może być używany, gdy opakowanie musi respektować określony interfejs i musi obsługiwać zachowanie polimorficzne . Alternatywnie dekorator umożliwia dodanie lub zmianę zachowania interfejsu w czasie wykonywania, a fasada jest używana, gdy pożądany jest łatwiejszy lub prostszy interfejs do obiektu znajdującego się pod spodem.

Wzór Zamiar
Adapter lub owijarka Konwertuje jeden interfejs na inny, aby odpowiadał oczekiwaniom klienta
Dekorator Dynamicznie dodaje odpowiedzialność do interfejsu, owijając oryginalny kod
Delegacja Wsparcie „składu nad dziedziczeniem”
Fasada Zapewnia uproszczony interfejs

Struktura

Diagram klas UML

Przykładowy diagram klas UML dla wzorca projektowego adaptera.

W powyższym UML diagram klas The clientklasa, która wymaga targetinterfejsu nie można ponownie wykorzystać adapteeklasę bezpośrednio, ponieważ jego interfejs nie są zgodne z targetinterfejsem. Zamiast tego clientdziała poprzez adapterklasę, która implementuje targetinterfejs w kategoriach adaptee:

  • object adapterSposób realizuje targetinterfejs przekazując do adapteeobiektu w czasie działania ( adaptee.specificOperation()).
  • W class adaptersposób implementuje targetinterfejs przez dziedziczenie z adapteeklasy w czasie kompilacji ( specificOperation()).

Wzór adaptera obiektów

W tym wzorcu adaptera adapter zawiera instancję klasy, którą opakowuje. W takiej sytuacji adapter wykonuje wywołania instancji opakowanego obiektu .

Wzorzec adaptera obiektów wyrażony w UML
Wzorzec adaptera obiektów wyrażony w LePUS3

Wzór adaptera klasy

Ten wzorzec adaptera używa wielu interfejsów polimorficznych implementujących lub dziedziczących zarówno interfejs oczekiwany, jak i interfejs już istniejący. Typowe jest to, że oczekiwany interfejs jest tworzony jako czysta klasa interfejsu , szczególnie w językach takich jak Java (przed JDK 1.8), które nie obsługują wielokrotnego dziedziczenia klas.

Wzorzec adaptera klasy wyrażony w UML .
Wzorzec adaptera klasy wyrażony w LePUS3

Kolejna forma wzorca adaptera środowiska uruchomieniowego

Motywacja z rozwiązania czasu kompilacji

Pożądane jest classAdostarczenie classBpewnych danych, załóżmy, że jakieś Stringdane. Rozwiązanie czasu kompilacji to:

classB.setStringData(classA.getStringData());

Załóżmy jednak, że należy zmienić format danych ciągu. Rozwiązaniem czasu kompilacji jest użycie dziedziczenia:

public class Format1ClassA extends ClassA {
    @Override
    public String getStringData() {
        return format(toString());
    }
}

i być może utworzy poprawnie obiekt „formatujący” w czasie wykonywania za pomocą wzorca fabryki .

Rozwiązanie adaptera czasu pracy

Rozwiązanie wykorzystujące „adaptery” przebiega w następujący sposób:

  1. Zdefiniuj pośredniczący interfejs „dostawcy” i napisz implementację tego interfejsu dostawcy, która otacza źródło danych, ClassAw tym przykładzie, i wyprowadza dane odpowiednio sformatowane:
    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. Napisz klasę adaptera, która zwraca konkretną implementację dostawcy:
    public class ClassAFormat1Adapter extends Adapter {
        public Object adapt(final Object anObject) {
            return new ClassAFormat1((ClassA) anObject);
        }
    }
    
  3. Zarejestruj w adapterrejestrze globalnym, aby adaptermożna było go wyszukać w czasie wykonywania:
    AdapterFactory.getInstance().registerAdapter(ClassA.class, ClassAFormat1Adapter.class, "format1");
    
  4. W kodzie, chcąc przenieść dane z ClassAdo ClassB, napisz:
    Adapter adapter =
        AdapterFactory.getInstance()
            .getAdapterFromTo(ClassA.class, StringProvider.class, "format1");
    StringProvider provider = (StringProvider) adapter.adapt(classA);
    String string = provider.getStringData();
    classB.setStringData(string);
    

    lub bardziej zwięźle:

    classB.setStringData(
        ((StringProvider)
                AdapterFactory.getInstance()
                    .getAdapterFromTo(ClassA.class, StringProvider.class, "format1")
                    .adapt(classA))
            .getStringData());
    
  5. Zaletą jest to, że jeśli chcesz przesłać dane w drugim formacie, wyszukaj inny adapter/dostawcę:
    Adapter adapter =
        AdapterFactory.getInstance()
            .getAdapterFromTo(ClassA.class, StringProvider.class, "format2");
    
  6. A jeśli chcesz wyprowadzić dane z ClassA, powiedzmy, danych obrazu w : Class C
    Adapter adapter =
        AdapterFactory.getInstance()
            .getAdapterFromTo(ClassA.class, ImageProvider.class, "format2");
    ImageProvider provider = (ImageProvider) adapter.adapt(classA);
    classC.setImage(provider.getImage());
    
  7. W ten sposób użycie adapterów i dostawców umożliwia wiele „widoków” przez ClassBi ClassCdo wnętrza ClassAbez konieczności zmiany hierarchii klas. Ogólnie pozwala na mechanizm dowolnego przepływu danych między obiektami, który można doposażyć w istniejącą hierarchię obiektów.

Implementacja wzorca adaptera

Podczas implementacji wzorca adaptera, dla jasności, można zastosować nazwę klasy do implementacji dostawcy; na przykład . Powinien mieć metodę konstruktora ze zmienną klasy adaptee jako parametrem. Ten parametr zostanie przekazany do elementu członkowskiego instancji . Gdy metoda clientMethod zostanie wywołana, będzie miała dostęp do instancji adaptera, która umożliwia dostęp do wymaganych danych adaptera i wykonywanie operacji na tych danych, które generują pożądane dane wyjściowe. [ClassName]To[Interface]AdapterDAOToProviderAdapter[ClassName]To[Interface]Adapter

Jawa

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));
    }
}

Wyjście

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

Pyton

"""
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();
}

Wyjście:

Apple phone connected.
Adapter cable connected.
Apple phone recharging.

Zobacz też

Bibliografia