Przeznaczenie

Należy do wzorców strukturalnych. Jego celem jest umożliwienie współpracy dwóch klas o niekompatybilnych interfejsach lub opakowanie starego interfejsu w nowy.

Adapter dwukierunkowy to specjalny rodzaj adaptera, którysprawie, że obie klasy mogą pełnić rolę klienta i adaptowanego. Można go zaadaptować tylko za pomocą wielokrotnego dziedziczenia.

 

Dlaczego go potrzebujemy?

Załóżmy, że mamy urządzenie, np. drukarkę, która ma niewyjmowalny kabel przesyłowy zakończony wtyczką USB, którą chcemy podłączyć do laptopa, żeby wydrukować zdjęcie.

Oto przykładowy kod:

package usb;

public interface UsbDeviceSocket {
    void plugIn();
}
 

Klasa typu interfejs znajdująca się w urządzeniu, która odpowiada za przesyłanie danych przez kabel USB. 

package usb;

public class UsbPlug {
    public void receiveData(UsbDeviceSocket socket){
        socket.plugIn();
    }
}
 

Klasa, która odbiera przesyłane dane, znajdująca się np. w naszej drukarce.

import usb.UsbPlug;
import usb.UsbSocketInLaptop;

public class Main {
    public static void main(String[] args) {
        UsbDeviceSocket usbSocket = new UsbDeviceSocket() {
            @Override
            public void plugIn() {
                System.out.println("Zdjęcie w formacie JPG.");
            }
        };
        UsbPlug usbPlug = new UsbPlug();
        usbPlug.receiveData(usbSocket);
    }
} 

Przesłanie danych ze zdjęciem do urządzenia

Zdjęcie w formacie JPG. 
Output

Wszystko tu pięknie działa dopóki mamy urządzenia z wejściem typu usb. A co się stanie, gdy spróbujemy podłączyć ten kabel do naszego telefonu z wejściem typu usb-c? Program się nie skompiluje i tu wkracza nasz adapter.

Wykorzystanie

Celem tego wzorca jest stworzenie klasy, która będzie dziedziczyć interfejs jednej klasy i adaptować jej metody by byly zrozumiałe, dla innej klasy. Co więcej, klasa „opakowana” przez adapter może nawet nie być tego faktu świadoma.

Schemat

Implementacja

Tworzymy nowy package dla usbC.

package usbC;

public interface UsbCDeviceSocket {
    void plugIn();
}
 

Klasa typu interfejs, która znajduje się w urządzeniu z wyjściem usb typu C

import usb.UsbDeviceSocket;
import usbC.UsbCDeviceSocket;

public class usbToCAdapter implements UsbDeviceSocket {
    UsbCDeviceSocket usbCDeviceSocket;

    public usbToCAdapter(UsbCDeviceSocket usbCDeviceSocket) {
        this.usbCDeviceSocket = usbCDeviceSocket;
    }

    @Override
    public void plugIn() {
        usbCDeviceSocket.plugIn();
    }
}
 

Adapter, który implementuje interfejs urządzenia z wejściem usb. W konstruktorze przyjmuje wejście usb typu C i nadpisuje metodę interdfejsu, żeby móc w niej wywołać metodę urządzenia z wejściem usb typu C.

import usb.UsbDeviceSocket;
import usb.UsbPlug;
import usbC.UsbCDeviceSocket;

public class Main {
    public static void main(String[] args) {
        UsbDeviceSocket usbSocket = new UsbDeviceSocket() {
            @Override
            public void plugIn() {
                System.out.println("Zdjęcie w formacie JPG.");
            }
        };
        UsbPlug usbPlug = new UsbPlug();
        usbPlug.receiveData(usbSocket);

        UsbCDeviceSocket usbCDeviceSocket = new UsbCDeviceSocket() {
            @Override
            public void plugIn() {
                System.out.println("Zdjęcie z telefonu.");
            }
        };

        usbToCAdapter usbToCAdapter = new usbToCAdapter(usbCDeviceSocket);
        usbToCAdapter.plugIn();
    }
} 

Wywołanie programu w którym przesyłamy dane z urządzenia z usb typu C poprzez adapter do naszego urządzenia, np. drukarki.

Zdjęcie w formacie JPG.
Zdjęcie z telefonu. 

Output

Podsumowanie

Zalety

  • Może oddzielić interfejs lub kod konwertujący dane od głównej logiki biznesowej programu
  •  Można wprowadzać nowe typy adaterów, bez psucia istniejącego kodu

Wady

  • Zwieksza złożoność kodu
  •  Przy obsłudze REST API należy tworzyć kolejne adaptery do obsługi kolejnych wersji