W świecie programowania często spotykamy się z sytuacją, w której chcemy użyć istniejącej klasy, ale jej interfejs nie pasuje do tego, czego oczekuje nasz system. Wzorzec projektowy Adapter umożliwia współpracę klas o niekompatybilnych interfejsach.
Zamiast przepisywać kod lub zmieniać istniejące klasy, możemy zastosować wzorzec projektowy Adapter. Adapter „tłumaczy” interfejs jednej klasy na interfejs oczekiwany przez drugą klasę, bez modyfikacji istniejącego kodu.
Rozważmy aplikację pozwalającą sprawdzić prognozę pogody – WeatherClient. Aplikacja wymaga do działania obiektu implementującego interfejs WeatherService i wywołuje na tym obiekcie metodę getCurrentTemperature().
public class WeatherClient {
WeatherService weatherService;
public WeatherClient(WeatherService weatherService) {
this.weatherService = weatherService;
}
public void showTemperature (String city) {
System.out.println(city + ": " + weatherService.getCurrentTemperature(city));
}
}
Interfejs WeatherService deklaruje 1 metodę, getCurrentTemperature(), zwracającą String:
public interface WeatherService {
String getCurrentTemperature(String city);
}
Ale prognoza pogody jest pobierana z zewnętrznego API. Interfejs ExternalWeatherApi deklaruje 1 metodę getTemperatureCelsius() zwracającą double:
public interface ExternalWeatherApi {
public double getTemperatureCelsius (String city);
}
Do kontaktu z API potrzebny jest obiekt implementujący ExternalWeatherApi.
Niestety nie można go przekazać do WeatherClient bo WeatherClient wymaga obiektu implementującego WeatherService :
public class Main {
public static void main (String[] args) {
ExternalWeatherApi weatherApi = new ExternalWeatherApi() {
@Override
public double getTemperatureCelsius(String city) {
return 15.2;
}
};
// WeatherClient weatherClient = new WeatherClient(weatherApi); // ERROR
}
}
Próba przekazania obiektu weatherApi do WeatherClient skutkuje błędem. Adapter pozwala rozwiązać ten problem.
Implementacja wzorca Adapter
Adapter dopasowuje obiekt implementujący ExternalWeatherApi do WeatherService. Klasa ExternalApiToWeatherServiceAdapter implementuje interfejs WeatherService (wymagany przez WeatherClient) i posiada obiekt implementujący ExternalWeatherApi (potrzebny do kontaktu z API) jako pole.
public class ExternalApiToWeatherServiceAdapter implements WeatherService{
ExternalWeatherApi weatherApi;
public ExternalApiToWeatherServiceAdapter(ExternalWeatherApi weatherApi) {
this.weatherApi = weatherApi;
}
@Override
public String getCurrentTemperature(String city) {
return weatherApi.getTemperatureCelsius(city) + " st. C";
}
}
Dzięki temu do można przekazać obiekt klasy ExternalApiToWeatherServiceAdapter do WeatherClient i wywołać na nim metodę getCurrentTemperature() zaimplementowaną z interfejsu WeatherService:
public class Main {
public static void main (String[] args) {
ExternalWeatherApi weatherApi = new ExternalWeatherApi() {
@Override
public double getTemperatureCelsius(String city) {
return 15.2;
}
};
ExternalApiToWeatherServiceAdapter apiAdapter =
new ExternalApiToWeatherServiceAdapter(weatherApi);
WeatherClient weatherClient = new WeatherClient(apiAdapter);
weatherClient.showTemperature("Łódź");
}
}
Po uruchomieniu programu w konsoli zostanie wypisany komunikat: Łódź: 15.2 st. C
Podsumowanie
Wzorzec Adapter to eleganckie rozwiązanie problemu niekompatybilnych interfejsów. Pozwala na ponowne wykorzystanie istniejącego kodu bez jego modyfikacji. Dzięki Adapterowi możemy łatwo integrować różne komponenty, zachowując spójność i elastyczność architektury.
Adapter jest powszechnie stosowany w integracjach z zewnętrznymi API, bibliotekami i systemami legacy. Jest szczególnie przydatny w integracji starszych systemów z nowymi komponentami. W komercyjnym użyciu adapter rzadko składa się z wywołania jednej metody. Przeważnie dochodzi w nim do przepakowania całych obiektów tak żeby przenieść je w całości z jednego interfejsu do innego.
Istnieje też adapter dwukierunkowy, który pozwala za pomocą jednego obiektu adaptować interfejsy w obie strony. Taki adapter musi implementować oba interfejsy. Adapter dwukierunkowy jest jednak mniej czytelny i rzadziej używany.
Wzorzec Adapter jest podobny do wzorca Fasada, ale różni się od niego intencją:
- celem wzorca Adapter jest modyfikacja interfejsu tak żeby dopasować go do potrzeb klienta,
- celem wzorca Fasada jest zapewnienie klientowi uproszczonego interfejsu dla danego systemu lub podsystemu.
