Wzór adaptera
W inżynierii oprogramowania wzorzec adaptera to wzorzec projektowy oprogramowania (znany również jako opakowanie , alternatywne nazewnictwo współdzielone ze wzorcem dekoratora ), który umożliwia użycie interfejsu istniejącej klasy jako innego interfejsu. Jest często używany, aby istniejące klasy działały z innymi bez modyfikowania ich kodu źródłowego .
Przykładem jest adapter, który konwertuje interfejs Document Object Model dokumentu XML na strukturę drzewa, która może być wyświetlana.
Przegląd
Wzorzec projektowy adaptera jest jednym z dwudziestu trzech dobrze znanych wzorców projektowych Gang of Four , które opisują, jak rozwiązywać powtarzające się problemy projektowe w celu zaprojektowania elastycznego i wielokrotnego użytku oprogramowania zorientowanego obiektowo, czyli obiektów łatwiejszych do wdrożenia, zmiany, przetestowania i ponownie użyć.
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ą niekompatybilne interfejsy, mogą ze sobą współpracować?
- W jaki sposób można 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 sposób rozwiązywania takich problemów:
- Zdefiniuj oddzielną klasę
adaptera
, która konwertuje (niekompatybilny) interfejs klasy (adaptee
) na inny interfejs (target
) wymagany przez klientów. - Pracuj z
adapterem
, aby pracować z (ponownie używanymi) klasami, które nie mają wymaganego interfejsu.
Kluczową ideą tego wzorca jest praca z oddzielnym adapterem
, który dostosowuje interfejs (już istniejącej) klasy bez jej zmiany.
Klienci nie wiedzą, czy pracują z klasą docelową
bezpośrednio, czy za pośrednictwem adaptera
z klasą, która nie ma interfejsu docelowego
.
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 projektowy adaptera umożliwia współpracę niekompatybilnych klas poprzez konwersję interfejsu jednej klasy na interfejs oczekiwany przez klientów.
Stosowanie
Adaptera można użyć, gdy opakowanie musi przestrzegać określonego interfejsu i musi obsługiwać zachowanie polimorficzne . Alternatywnie, dekorator umożliwia dodawanie lub zmianę zachowania interfejsu w czasie wykonywania, a fasada jest używana, gdy pożądany jest łatwiejszy lub prostszy interfejs do obiektu leżącego poniżej.
Wzór | Zamiar |
---|---|
Adapter lub opakowanie | Konwertuje jeden interfejs na inny, tak aby odpowiadał oczekiwaniom klienta |
Dekorator | Dynamicznie dodaje odpowiedzialność do interfejsu, zawijając oryginalny kod |
Delegacja | Wspieraj „kompozycję zamiast dziedziczenia” |
Fasada | Zapewnia uproszczony interfejs |
Struktura
Diagram klas UML
Na powyższym diagramie klas UML klasa klienta, która wymaga interfejsu docelowego
, nie może bezpośrednio ponownie użyć klasy adaptee
,
ponieważ jej interfejs nie jest zgodny z interfejsem docelowym
. Zamiast tego klient
działa poprzez klasę adaptera
, która implementuje interfejs docelowy
w kategoriach adaptee
:
- Sposób
adaptera obiektów
implementuje interfejsdocelowy
poprzez delegowanie do obiektuadaptee
w czasie wykonywania (adaptee.specificOperation()
). -
adaptera klas
implementuje docelowyinterfejs
poprzez dziedziczenie z klasyadaptee
w czasie kompilacji (specificOperation()
).
Wzorzec adaptera obiektu
W tym wzorcu adaptera adapter zawiera instancję klasy, którą opakowuje. W tej sytuacji adapter wykonuje wywołania instancji opakowanego obiektu .
Wzorzec adaptera klasy
Ten wzorzec adaptera wykorzystuje wiele interfejsów polimorficznych , które implementują lub dziedziczą zarówno oczekiwany interfejs, jak i istniejący wcześniej interfejs. Typowe jest, że oczekiwany interfejs jest tworzony jako czysta interfejsu , szczególnie w językach takich jak Java (przed JDK 1.8), które nie obsługują wielokrotnego dziedziczenia klas.
Dalsza forma wzorca adaptera środowiska wykonawczego
Motywacja z rozwiązania czasu kompilacji
Pożądane jest, aby klasa A
dostarczała klasie B
pewne dane, załóżmy, że niektóre dane typu String .
Rozwiązaniem czasu kompilacji jest:
klasa B. setStringData ( klasa A. getStringData ( ));
Załóżmy jednak, że format danych ciągu musi być zróżnicowany. Rozwiązaniem czasu kompilacji jest użycie dziedziczenia:
klasa publiczna Format1ClassA rozszerza ClassA { @Override public String getStringData () { format powrotu ( toString ()); } }
i być może utwórz poprawnie „formatujący” obiekt w czasie wykonywania za pomocą wzorca fabrycznego .
Rozwiązanie adaptera czasu pracy
Rozwiązanie z wykorzystaniem „adapterów” przebiega w następujący sposób:
- Zdefiniuj pośredni interfejs „dostawcy” i napisz implementację tego interfejsu dostawcy, która opakowuje źródło danych, w tym przykładzie
ClassA
, i wyprowadza dane w odpowiednim formacie:publiczny interfejs StringProvider { publiczny ciąg getStringData (); } public class ClassAFormat1 implementuje StringProvider { private ClassA classA = null ; public ClassAFormat1 ( final ClassA a ) { classA = a ; } public String getStringData () { zwracany format ( classA . getStringData ()); } private String format ( final String sourceValue ) { // Zmodyfikuj łańcuch źródłowy do formatu wymaganego // przez obiekt wymagający zwrotu danych obiektu źródłowego sourceValue . przycinanie (); } }
- Napisz klasę adaptera, która zwraca konkretną implementację dostawcy:
klasa publiczna ClassAFormat1Adapter rozszerza adapter { public Object adapt ( final Object anObject ) { return new ClassAFormat1 (( ClassA ) anObject ); } }
- Zarejestruj
adapter
w rejestrze globalnym, abyadapter
mógł być wyszukiwany w czasie wykonywania:Fabryka adapterów . pobierzInstancję (). registerAdapter ( ClassA . klasa , ClassAFormat1Adapter . klasa , "format1" );
- W kodzie, chcąc przenieść dane z
ClassA
doClassB
, napisz:Adapter adaptera = Fabryka adapterów . getInstance () . getAdapterFromTo ( ClassA . klasa , StringProvider . klasa , "format1" ); Dostawca StringProvider = adapter ( StringProvider ) . dostosować ( klasa A ); Ciąg znaków = dostawca . getStringData (); klasa B. setStringData ( string );
lub bardziej zwięźle:
klasa B. setStringData ( (( StringProvider ) AdapterFactory . getInstance () . getAdapterFromTo ( ClassA . class , StringProvider . class , "format1" ) . adapt ( class A )) . getStringData ());
- Zaletą jest to, że jeśli chcesz przesłać dane w drugim formacie, poszukaj innego adaptera/dostawcy:
Adapter adaptera = Fabryka adapterów . getInstance () . getAdapterFromTo ( ClassA . klasa , StringProvider . klasa , "format2" );
- A jeśli pożądane jest wyprowadzenie danych z
klasy A
jako, powiedzmy, danych obrazu wklasie C
:Adapter adaptera = Fabryka adapterów . getInstance () . getAdapterFromTo ( Klasa A. klasa , ImageProvider . klasa , "format2" ); Dostawca ImageProvider = adapter ( ImageProvider ) . dostosować ( klasa A ); klasa C . setImage ( dostawca . getImage ());
- W ten sposób użycie adapterów i dostawców umożliwia wiele „widoków” z
klasy B
iklasy C
doklasy A
bez konieczności zmiany hierarchii klas. Ogólnie rzecz biorąc, umożliwia mechanizm dowolnego przepływu danych między obiektami, który można zmodernizować do istniejącej hierarchii obiektów.
Implementacja wzorca adaptera
Podczas implementacji wzorca adaptera, dla jasności, można zastosować nazwę klasy [ ClassName ] To [ Interface ] Adapter
do implementacji dostawcy; na przykład DAOToProviderAdapter
. Powinien mieć metodę konstruktora ze zmienną klasy adaptee jako parametr. Ten parametr zostanie przekazany do elementu członkowskiego instancji adaptera [ nazwa klasy ] do [ interfejsu ]
. Po wywołaniu metody clientMethod będzie ona miała dostęp do instancji osoby adaptującej, która umożliwia dostęp do wymaganych danych osoby adaptowanej i wykonywanie operacji na tych danych, które generują pożądane dane wyjściowe.
Jawa
interfejs ILightningPhone { unieważnić ładowanie (); unieważnić użycie Błyskawicy (); } interfejs IMicroUsbPhone { unieważnić ładowanie (); nieważne użycieMicroUsb (); } class Iphone implementuje ILightningPhone { private boolean connector ; @Override public void useLightning () { złącze = true ; systemu . na zewnątrz println ( "Błyskawica połączona" ); } @Override public void doładowanie () { if ( łącznik ) { System . na zewnątrz println ( "Doładowanie rozpoczęte" ); systemu . na zewnątrz println ( "Ładowanie zakończone" ); } else { System . na zewnątrz println ( "Najpierw podłącz Lightning" ); } } } klasa Android implementuje IMicroUsbPhone { private boolean connector ; @Override public void useMicroUsb () { złącze = true ; systemu . na zewnątrz println ( "Połączono z MicroUSB" ); } @Override public void doładowanie () { if ( łącznik ) { System . na zewnątrz println ( "Doładowanie rozpoczęte" ); systemu . na zewnątrz println ( "Ładowanie zakończone" ); } else { System . na zewnątrz println ( "Najpierw podłącz MicroUsb" ); } } } / * eksponowanie interfejsu docelowego podczas zawijania obiektu źródłowego */ class LightningToMicroUsbAdapter implements IMicroUsbPhone { private final ILightningPhone lightningPhone ; public LightningToMicroUsbAdapter ( ILightningPhone lightningPhone ) { this . błyskawicaTelefon = błyskawicaTelefon ; } @Override public void useMicroUsb () { System . na zewnątrz println ( "Połączono z MicroUSB" ); BłyskawicaTelefon . użyj Błyskawicy (); } @Override public void doładowanie () { lightningPhone . naładować (); } } Public Class AdapterDemo { static void rechargeMicroUsbPhone ( telefon IMicroUsbPhone ) { telefon . użyjMicroUsb (); telefon . naładować (); } static void doładowanie LightningPhone ( ILightningPhone phone ) { phone . użyj Błyskawicy (); telefon . naładować (); } public static void main ( String [] args ) { Android android = nowy Android (); iPhone iPhone = nowy iPhone (); systemu . na zewnątrz println ( "Ładowanie Androida przez MicroUsb" ); naładuj MicroUsbPhone ( Android ); systemu . na zewnątrz println ( "Ładowanie iPhone'a za pomocą Lightning" ); naładujLightningPhone ( iPhone ); systemu . na zewnątrz println ( "Ładowanie iPhone'a przez MicroUsb" ); naładuj MicroUsbPhone ( nowy LightningToMicroUsbAdapter ( iPhone )); } }
Wyjście
Ładowanie Androida z MicroUsb MicroUsb podłączone Ładowanie rozpoczęte Ładowanie zakończone Ładowanie iPhone'a z Lightning Lightning połączone Ładowanie rozpoczęte Ładowanie zakończone Ładowanie iPhone'a z MicroUsb MicroUsb połączone Lightning połączone Ładowanie rozpoczęte Ładowanie zakończone
Pyton
""" Przykład wzorca adaptera. """ from abc import ABCMeta , abstractmethod NOT_IMPLEMENTED = "Powinieneś to zaimplementować." RECHARGE = [ "Rozpoczęło się ładowanie." , „Ładowanie zakończone”. ] POWER_ADAPTERS = { "Android" : "MicroUSB" , "iPhone" : "Lightning" } CONNECTED = " {} połączono." CONNECT_FIRST = "Najpierw połącz {} ." klasa recargetemplate ( metaclass = abcmeta ): @abstractmethod def recarge ( self ) : podnieś notImplementedError ( not_implemented ) classatiphone ( rechargetEmplate ): @abstractmethod def użyte_lightning ( self ) : Raise NotImplementEdror ( nie_implementedAdandRoid ( rechargeMplate ) : @AbStamplate ) : @AbStamPlate ) _Micro_USB ( self ): podnieś NotImplementedError ( NOT_IMPLEMENTED ) klasa IPhone ( FormatIPhone ): __name__ = "iPhone" def __init__ ( self ): self . złącze = False def use_lightning ( self ): self . złącze = True print ( CONNECTED . format ( POWER_ADAPTERS [ self . __name__ ])) def recharge ( self ): if self . złącze : dla stanu w RECHARGE : print ( stan ) else : print ( CONNECT_FIRST . format ( PIERWSZY_PRZEŁĄCZNIKI [ self . __name__ ])) klasa Android ( FormatAndroid ): __name__ = "Android" def __init__ ( self ): self . złącze = False def use_micro_usb ( self ): self . złącze = True print ( CONNECTED . format ( POWER_ADAPTERS [ self . __name__ ])) def recharge ( self ): if self . złącze : dla stanu w RECHARGE : print ( stan ) else : print ( CONNECT_FIRST . format ( POWER_ADAPTERS [ self . __name__ ])) class IPhoneAdapter ( FormatAndroid ): def __init__ ( self , mobile ): self . mobile = mobile def doładowanie ( self ): self . mobilny . recharge () def use_micro_usb ( self ): print ( CONNECTED . format ( POWER_ADAPTERS [ "Android" ])) self . mobilny . use_lightning () klasa AndroidRecharger : def __init__ ( self ): self . telefon = sam Android () . telefon . use_micro_usb () sam . telefon . ładowanie () klasa IPhoneMicroUSBRecharger : def __init__ ( self ): self . telefon = IPhone () sam . phone_adapter = IPhoneAdapter ( self . phone ) self . telefon_adapter . use_micro_usb () sam . telefon_adapter . ładowanie () klasa IPhoneRecharger : def __init__ ( self ): self . telefon = IPhone () sam . telefon . use_lightning () siebie . telefon . recharge () print ( „Ładowanie Androida za pomocą ładowarki MicroUSB.” ) AndroidRecharger () print () print ( „Ładowanie iPhone'a za pomocą ładowarki MicroUSB przy użyciu wzoru adaptera.” ) IPhoneMicroUSBRecharger () print () print ( „Ładowanie iPhone'a za pomocą ładowarki iPhone'a.” ) Ładowarka iPhone'a ()
C#
interfejs publiczny ILightningPhone { unieważnić ConnectLightning (); anuluj ładowanie (); } publiczny interfejs IUsbPhone { nieważne ConnectUsb (); anuluj ładowanie (); } publiczna zapieczętowana klasa AndroidPhone : IUsbPhone { private bool isConnected ; public void ConnectUsb () { to . isConnected = true ; Konsola . WriteLine ( "Połączono telefon z Androidem." ); } public void Recharge () { if ( this . isConnected ) { Konsola . WriteLine ( "Ładowanie telefonu z Androidem." ); } else { Konsola . WriteLine ( "Najpierw podłącz kabel USB." ); } } } publiczna zapieczętowana klasa ApplePhone : ILightningPhone { private bool isConnected ; public void ConnectLightning () { to . isConnected = true ; Konsola . WriteLine ( "Połączono telefon Apple." ); } public void Recharge () { if ( this . isConnected ) { Konsola . WriteLine ( "Ładowanie telefonu Apple." ); } else { Konsola . WriteLine ( "Najpierw podłącz kabel Lightning." ); } } } publiczna zapieczętowana klasa LightningToUsbAdapter : IUsbPhone { private tylko do odczytu ILightningPhone lightningPhone ; prywatny bool jest podłączony ; public LightningToUsbAdapter ( ILightningPhone lightningPhone ) { this . błyskawicaTelefon = błyskawicaTelefon ; to . BłyskawicaTelefon . Połącz Błyskawicę (); } public void ConnectUsb () { to . isConnected = true ; Konsola . WriteLine ( "Kabel adaptera podłączony." ); } public void Recharge () { if ( this . isConnected ) { this . BłyskawicaTelefon . Naładuj (); } else { Konsola . WriteLine ( "Najpierw podłącz kabel USB." ); } } } public void Main () { ILightningPhone applePhone = nowy ApplePhone (); IUsbPhone adapterCable = nowy LightningToUsbAdapter ( applePhone ); adapter Kabel . PołączUsb (); adapter Kabel . Naładuj (); }
Wyjście:
Telefon Apple podłączony. Kabel adaptera podłączony. Ładowanie telefonu Apple.
Zobacz też
- Adapter Wzorce projektowe Java — Adapter
- Delegacja , silnie związana ze wzorcem adaptera obiektów.
- Zasada odwrócenia zależności , którą można traktować jako zastosowanie wzorca adaptera, gdy klasa wysokiego poziomu definiuje swój własny (adapter) interfejs do modułu niskiego poziomu (zaimplementowanego przez klasę adaptee).
- Architektura portów i adapterów
- Podkładka
- Funkcja owijania
- Biblioteka opakowań