Zastosowanie
Adapter
(ang. Adapter
) (wzorzec strukturalny) umożliwia współpracę dwóch klas o niekompatybilnych interfejsach dzięki przekształceniu interfejsu jednej klasy na zrozumiałą postać dla drugiej. Adapter
zwany inaczej Wrapper
jest swego rodzaju przejściówką między dwoma bytami. Wyróżnia się dwa warianty klasycznej postaci tego wzorca Adapter Klasowy
oraz Adapter Obiektowy
. Wybór rodzaju Adaptera
w zależności od warunków może być dowolny lub podyktowany ograniczeniami. W sytuacji gdy nie można rozszerzyć klasy adaptowanej używany jest Adapter Obiektowy
, natomiast gdy klasa adaptowana jest abstrakcyjna stosuje się Adapter Klasowy
. Ze względu zagrożenia jakie niesie ze sobą używanie Adaptera Klasowego
w większości sytuacji stosuje się Adapter Obiektowy
. Ponadto mówi się o Adapterze Dwukierunkowym
, w którym translacja zachodzi w obie strony, tzn. klient jest jest także klasą adapatowaną. Taki mechanizm może posłużyć np. do konwersji typów.
Ograniczenia
Przy tworzeniu Adaptera
należy mieć na uwadzę, że wraz ze wzrostem niekompatybilności maleje wydajność co może stanowić problem w niektórych aplikacjach. Adapter Obiektowy
nie ma możliwości przeładowania metod klasy adaptowanej, natomiast Adapter Klasowy
nie pozwala na adaptowanie podklas klasy adaptowanej. Adapter Klasowy
ściśle wiąże klienta z klasą adaptowaną co w przypadku rozrostu systemu czy zmiany wymagań zmniejsza reużywalność kodu.
Użycie
Stosowany jest głównie tam, gdzie wykorzystanie istniejącej klasy w celu komunikacji z inną klasą jest niemożliwe ze względu na niekompatybilność interfejsu. Ponadto Adapter
używany jest w sytuacjach, gdy tworzona klasa będzie współpracować z klasami o nieokreślonych interfejsach.
Implementacja
Adapter Klasowy
dziedziczy po klasie adaptowanej Adaptee
oraz implementuje klase klienta Client
. Adapter Obiektowy
implementuje klase klienta Client
oraz zawiera instancję klasy adaptowanej Adaptee
.
Klasa Client
oraz Adaptee
są niekompatybilne co przedstawia poniższy listing.
public interface Client {
void operation1();
void operation2();
}
public class Adaptee {
public void method1() {
//some work
}
public void method2() {
//some work
}
public void method3() {
//some work
}
}
Aby możliwa była komunikacja należy między nimi wprowadzić Adapter
. Poniższy listing przedstawia implementacje wzorca w dwóch wariantach.
public class AdapterClass extends Adaptee implements Client {
@Override
public void operation1() {
method1();
}
@Override
public void operation2() {
method2();
}
}
public class AdapterObject implements Client {
private Adaptee adaptee;
public Client() {
this.adaptee = new Adaptee();
//some parameters can be passed to initialize Adaptee
}
@Override
public void operation1() {
adaptee.method1();
}
@Override
public void operation2() {
adaptee.method2();
}
}
Tak przygotowane adaptery umożliwią wywołanie metod klasy adaptowanej Adaptee
z poziomu klasy Client
co przedstawia poniższy listing.
Client clientClass = new AdapterClass();
clientClass.operation1(); //runs method1() from Adaptee
Client clientObject = new AdapterObject();
clientObject.operation2(); //runs method2() from Adaptee
Przykład
Komunikator Messenger
umożliwia wysyłanie wiadomości tekstowych oraz plików między rozmówcami wykorzystując w tym celu wewnętrzną biblioteke protokołu sieciowego. Ponadto aplikacja pozwala na zmianę statusu. Poniższy listing przedstawia implementacje komunikatora Messenger
wraz z jego protokołem MessageProtocol
.
public class Messenger {
private IMessageProtocol protocol;
public Messenger(IMessageProtocol protocol) {
this.protocol = protocol;
}
public void sendMessage(String text) {
protocol.sendText(text);
}
public void sendMessage(File file) {
protocol.sendFile(file);
}
public void changeStatus(String text) {
//set and show status
}
}
public class MessageProtocol implements IMessageProtocol {
@Override
public void sendText(String text) {
//converting and sending text
}
@Override
public void sendFile(File file) {
//converting and sending file
}
}
public interface IMessageProtocol {
void sendText(String text);
void sendFile(File file);
}
W pewnym momencie cyklu życia projektu zachodzi potrzeba wymiany protokołu sieciowego MessageProtocol
na bardziej wydajną biblioteke zewnętrzną ExternalMessageProtocol
. Dodatkowo każda operacja ma wysyłać dane do analityki. Ze względu na rozmiar projektu nie wchodzi w grę zmiana typu instancji protokołu oraz wywołań metod we wszystkich wystąpieniach obiektu klasy Messenger
. Rozwiązaniem tego problemu jest zastosowanie Adaptera
, którego implementacja przedstawiona jest poniżej.
public class MessageProtocolAdapter implements IMessageProtocol {
private ExternalMessageProtocol protocol = new ExternalMessageProtocol();
public MessageProtocolAdapter() {
this.protocol = new ExternalMessageProtocol();
}
@Override
public void sendText(String text) {
Analytics.report("Message");
protocol.sendString(text);
}
@Override
public void sendFile(File file) {
Analytics.report("File");
protocol.sendBytes(protocol.getBytes(file));
}
}
Aby skorzystać z nowego rozwiązania wystarczy przekazać referencje Adaptera
do aplikacji w następujący sposób.
Messenger newMessenger = new Messenger(new MessageProtocolAdapter());
newMessenger.sendMessage("Message from external library"); //send optimized message and analytics
Messenger oldMessenger = new Messenger(new MessageProtocol());
oldMessenger.sendMessage("Message from internal library"); //send only message
Biblioteki
Adapter
jest standardowym elementem biblioteki Android
. Jego celem jest dostarczenie danych do widoku kolekcji AdaperView
, a także stworzenie widoków View
dla każdego elementu ze zbioru danych. Metody asList
klasy Arrays
oraz list
klasy Collections
są przykładem implementacji wzorca w standardowym pakiecie Java
.