Model de adaptor - Adapter pattern

În ingineria software , modelul adaptorului este un model de proiectare software (cunoscut și sub numele de wrapper , o denumire alternativă împărtășită cu modelul decorator ) care permite ca interfața unei clase existente să fie utilizată ca altă interfață. Este adesea folosit pentru a face cursurile existente să lucreze cu alții fără a le modifica codul sursă .

Un exemplu este un adaptor care convertește interfața unui model de obiect document al unui document XML într-o structură de copac care poate fi afișată.

Prezentare generală

Modelul de proiectare a adaptorului este unul dintre cele douăzeci și trei de cunoscute modele de proiectare Gang of Four care descriu cum să rezolvați problemele de proiectare recurente pentru a proiecta software flexibil și reutilizabil orientat obiect, adică obiecte care sunt mai ușor de implementat, schimbat, testat , și reutilizarea.

Modelul de proiectare a adaptorului rezolvă probleme precum:

  • Cum poate fi reutilizată o clasă care nu are o interfață de care are nevoie un client?
  • Cum pot funcționa împreună clasele care au interfețe incompatibile?
  • Cum se poate furniza o interfață alternativă pentru o clasă?

Adesea, o clasă (deja existentă) nu poate fi reutilizată doar deoarece interfața sa nu este conformă cu interfața necesară clienților.

Modelul de proiectare a adaptorului descrie cum să rezolvați astfel de probleme:

  • Definiți o adapterclasă separată care convertește interfața (incompatibilă) a unei clase ( adaptee) într-o altă interfață ( target) necesară clienților.
  • Lucrați printr-o adapterpentru a lucra cu (reutilizați) clase care nu au interfața necesară.

Ideea cheie în acest model este de a lucra printr-un separat adaptercare adaptează interfața unei clase (deja existente) fără a o schimba.

Clienții nu știu dacă lucrează targetdirect cu o clasă sau prin intermediul unei adapterclase care nu are targetinterfața.

Vezi și diagrama clasei UML de mai jos.

Definiție

Un adaptor permite două interfețe incompatibile să lucreze împreună. Aceasta este definiția reală a unui adaptor. Interfețele pot fi incompatibile, dar funcționalitatea interioară ar trebui să se potrivească nevoilor. Modelul de proiectare a adaptorului permite claselor incompatibile să lucreze împreună prin conversia interfeței unei clase într-o interfață așteptată de clienți.

Utilizare

Un adaptor poate fi utilizat atunci când învelișul trebuie să respecte o anumită interfață și trebuie să susțină comportamentul polimorf . Alternativ, un decorator face posibilă adăugarea sau modificarea comportamentului unei interfețe în timpul rulării și o fațadă este utilizată atunci când se dorește o interfață mai ușoară sau mai simplă cu un obiect subiacent.

Model Intenție
Adaptor sau ambalaj Convertește o interfață în alta astfel încât să se potrivească cu ceea ce așteaptă clientul
Decorator Dinamic adaugă responsabilitate interfeței prin împachetarea codului original
Delegație Susțineți „compoziția peste moștenire”
Faţadă Oferă o interfață simplificată

Structura

Diagrama clasei UML

Image
Un exemplu de diagramă de clasă UML pentru modelul de proiectare a adaptorului.

În diagrama de clasă UML de mai sus , clientclasa care necesită o targetinterfață nu poate reutiliza adapteeclasa direct, deoarece interfața sa nu este conformă cu targetinterfața. În schimb, clientfuncționează printr-o adapterclasă care implementează targetinterfața în termeni de adaptee:

  • object adapterModul în care implementează targetinterfața de delegare a unui adapteeobiect în timpul rulării ( adaptee.specificOperation()).
  • Modul în class adaptercare implementează targetinterfața prin moștenirea de la o adapteeclasă la compile-time ( specificOperation()).

Model de adaptor de obiecte

În acest model de adaptor, adaptorul conține o instanță din clasa pe care o înfășoară. În această situație, adaptorul face apeluri către instanța obiectului împachetat .

Image
Modelul adaptorului de obiect exprimat în UML
Image
Modelul adaptorului de obiect exprimat în LePUS3

Model de adaptor de clasă

Acest model de adaptor utilizează mai multe interfețe polimorfe care implementează sau moștenesc atât interfața așteptată, cât și interfața preexistentă. Este tipic ca interfața așteptată să fie creată ca o clasă de interfață pură , în special în limbi precum Java (înainte de JDK 1.8) care nu acceptă moștenirea multiplă a claselor.

Image
Modelul adaptorului de clasă exprimat în UML .
Image
Modelul adaptorului de clasă exprimat în LePUS3

O altă formă de model de adaptor de execuție

Motivația din soluția de timp de compilare

Se dorește classAfurnizarea classBunor date, să presupunem unele Stringdate. O soluție de timp de compilare este:

classB.setStringData(classA.getStringData());

Cu toate acestea, să presupunem că formatul datelor șirului trebuie să fie variat. O soluție de timp de compilare este utilizarea moștenirii:

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

și poate creați obiectul „formatare” corect în timpul rulării prin intermediul modelului din fabrică .

Soluție adaptor în timp de execuție

O soluție care utilizează „adaptoare” se desfășoară după cum urmează:

  1. Definiți o interfață intermediară „furnizor” și scrieți o implementare a acelei interfețe furnizor care înfășoară sursa datelor, ClassAîn acest exemplu, și transmite datele formatate după caz:
    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. Scrieți o clasă de adaptor care returnează implementarea specifică a furnizorului:
    public class ClassAFormat1Adapter extends Adapter {
        public Object adapt(final Object anObject) {
            return new ClassAFormat1((ClassA) anObject);
        }
    }
    
  3. Înregistrați-l adaptercu un registru global, astfel încât să adapterpoată fi căutat la runtime:
    AdapterFactory.getInstance().registerAdapter(ClassA.class, ClassAFormat1Adapter.class, "format1");
    
  4. În cod, când doriți să transferați date de la ClassAcătre ClassB, scrieți:
    Adapter adapter =
        AdapterFactory.getInstance()
            .getAdapterFromTo(ClassA.class, StringProvider.class, "format1");
    StringProvider provider = (StringProvider) adapter.adapt(classA);
    String string = provider.getStringData();
    classB.setStringData(string);
    

    sau mai concis:

    classB.setStringData(
        ((StringProvider)
                AdapterFactory.getInstance()
                    .getAdapterFromTo(ClassA.class, StringProvider.class, "format1")
                    .adapt(classA))
            .getStringData());
    
  5. Avantajul poate fi văzut prin faptul că, dacă se dorește transferul datelor într-un al doilea format, atunci căutați diferitul adaptor / furnizor:
    Adapter adapter =
        AdapterFactory.getInstance()
            .getAdapterFromTo(ClassA.class, StringProvider.class, "format2");
    
  6. Și dacă se dorește să se obțină datele din ClassAimagini ca, să zicem, date în : Class C
    Adapter adapter =
        AdapterFactory.getInstance()
            .getAdapterFromTo(ClassA.class, ImageProvider.class, "format2");
    ImageProvider provider = (ImageProvider) adapter.adapt(classA);
    classC.setImage(provider.getImage());
    
  7. În acest fel, utilizarea de adaptoare și furnizori permite mai multe „puncte de vedere“ de către ClassBși ClassCîn ClassAfără a fi nevoie să modifice ierarhia de clasă. În general, permite un mecanism pentru fluxuri de date arbitrare între obiecte care pot fi adaptate la o ierarhie de obiecte existentă.

Implementarea modelului adaptorului

La implementarea modelului adaptorului, pentru claritate, se poate aplica numele clasei la implementarea furnizorului; de exemplu ,. Ar trebui să aibă o metodă constructor cu o variabilă de clasă adaptată ca parametru. Acest parametru va fi transmis unui membru al instanței din . Când se apelează clientMethod, acesta va avea acces la instanța adaptată care permite accesarea datelor necesare ale adaptatului și efectuarea operațiunilor pe acele date care generează ieșirea dorită. [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));
    }
}

Ieșire

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

Piton

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

Ieșire:

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

Vezi si

Referințe