Świat nie jest idealny. Wielokrotnie przyjdzie nam stawiać czoła problemom wynikającym z różnych niedoskonałości, czy to zeszłych czy też tych dopiero wprowadzonych. Aby im sprostać musimy nauczyć się improwizować i adaptować. Panie i panowie – oto Adapter.
Dzisiejszy wpis miał być poświęcony dwóm wzorcom: Adapterowi oraz Fasadzie, które faktycznie są do siebie trochę podobne, ale też na tyle różne, że chcąc je zmieścić w jednym artykule musiałbym albo go niemiłosiernie rozwlec, albo pozbawić wartościowych informacji. Lepiej zatem będzie jeśli rozłożymy to na dwie części.
Zacznijmy zatem od nakreślenia problemu, który rozwiąże nasz Adapter:
Macie własną appkę – pogodynkę. Dane meteorologiczne pobierane są ze stacji pogodowej zamontowanej na balkonie. Póki co wszystko działa elegancko, ale kupujecie nową stację z jeszcze lepszymi możliwościami… która temperaturę zwraca w stopniach Kelvina. Wszystko macie ustawione pod Celsjusza, więc nowe wyniki wyświetlane są kuriozalnie – co robicie?
public class Temperature{
void showTemperatureCelcius(Meteo meteo){
//obiekt 'meteo' zawiera wszystkie dane ze stacji
show(meteo.getTemperature)
}
}
Odpowiedź nasuwa się sama – zmieniacie klasę odpowiedzialną za wyświetlanie temperatury. Owszem, można, ale zgodnie z zasadą Otwarty/Zamknięty nazwijmy to planem B. Czy da radę coś z tym zrobić bez modyfikacji klasy klienta?
A jakże. Możemy skorzystać z wzorca Dekorator o którym już mówiliśmy. Stworzymy nową klasę, która opakuje klasę odpowiedzialną za wyświetlanie temperatury i podmieni wyniki:
Decorator decorator = Decorator(new Temperature);
decorator.showTemperatureCelcius(meteo);
Rozwiązanie proste i szybkie. Czego chcieć więcej?

W zasadzie wszystko jest już na swoim miejscu, problem może pojawić się jedynie kiedy takich dekoracji trzeba będzie zrobić więcej. Wtedy zauważymy, że liczba dodatkowych linijek rośnie i zaczyna zaciemniać kod. Przydałaby się bardziej elegancka solucja. I tu właśnie do gry wchodzi adapter.
O ile Dekorator „przypudrowuje” stary obiekt, tak aby spełniał nasze oczekiwania, o tyle Adapter podmienia go na nowy, na dokładnie taki jakiego potrzebujemy. Aby sobie to lepiej zobrazować weźmy za przykład auto. Stary gruz, który jakimś cudem wciąż jeździ i dowozi nas do pracy.

Ale my chcemy czegoś nowszego. Wydajemy więc kupę kasy na mechanika, lakiernika, blacharza i wulkanizatora, a na koniec kupujemy jeszcze Wunderbauma… gotowe! Autko wygląda jak nowe, a do tego pachnie sosenką!
Albo możemy inaczej – odstawiamy gruchota, a na jego miejsce kupujemy świeższy egzemplarz. Wszystko rozbija się o kwestię ceny – czasami w starszym modelu wystarczy wymienić opony, klocki i olej, wtedy nie ma co jeszcze myśleć o kupnie nowego samochodu. Ale czasami pracy jest tyle, że taniej nam wyjdzie zainwestować w coś prosto z salonu.
Przed podobnym dylematem możecie stanąć kiedy przyjdzie wam wybierać pomiędzy zastosowaniem Dekoratora i Adaptera próbując dostosować tzw. legacy code do swoich wymogów. Przypomnijmy definicję tego pierwszego i porównajmy z Adapterem:
Dekorator – pozwala na dynamiczne przydzielanie danemu obiektowi nowych zachowań. Dekoratory dają elastyczność podobną do tej, jaką daje dziedziczenie, oferując jednak w zamian znacznie rozszerzoną funkcjonalność.
Rusz głową! Wzorce projektowe
Wzorzec Adapter dokonuje konwersji intrfejsu danej klasy do postaci innego interfejsu, zgodnego z oczekiwaniami klienta. Adapter pozwala na wzajemną współpracę klas, które ze względu na niekompatybilne interfejsy wcześniej nie mogły ze sobą współpracować.
Dynamiczne przydzielanie vs. konwersja. Decyzja należy do nas.

Ale po kolei…
Jak zbudować dobrą drużynę?
A co znaczy „dobrą„? Chodzi o skuteczność? Czy o ciekawe prowadzenie? A może zależy nam na interakcjach pomiędzy członkami zespołu? Tu za przykład weźmy grę Icewind Dale, gdzie od samego początku tworzymy własny zespół wybierając klasy i losując statystyki. Od zera do bohatera.
Można naturalnie stworzyć sześciu siepaczy, i przejść w ten sposób całą grę. Już pomijając monotonię takiego podejścia, trzeba brać pod uwagę ograniczoną ilość ciekawych artefaktów – jednego albo dwóch wojowników jako tako ubierzemy, a reszta będzie musiała ganiać w skórzanych zbrojach. Zróżnicowany zespół to interesująca rozgrywka, równomierny podział lootu oraz masa taktyk z których znajdziemy coś na każdą okazję. Łotrzyk skrytobójca, łowca szyjący z dystansu, tarczownik biorący na siebie impet wroga, czarodziej kontrujący wrogich magów, no i rzecz jasna jakiś healer.
Kapłan czy druid?
Sernik z rodzynkami czy bez? Można wziąć to i to, ale większość zaklęć będzie się pokrywać. To też przydatne, bo im więcej czarów leczenia tym drużyna dłużej pociągnie. Ja jednak bardziej skłaniam się ku druidowi – co prawda nie wzmocni zespołu buffami tak dobrze jak rasowy kleryk, ale za to przywoła, albo oczaruje jakieś zwierzaki aby walczyły po naszej stronie. Szczególnie na początku rozgrywki to nieoceniona pomoc.

Przejdźmy do sedna – jak w kodzie sprawić aby przeciwnik stał się sprzymierzeńcem? W końcu oba korzystają z różnych interfejsów.
Spójrzcie poniżej. Pełny kod jak zwykle na GitHubie.

Adaptera używa się aby zniwelować różnicę w interfejsach. Mamy więc brunatnego miśka, który jest naszym wrogiem (interfejs Enemy) i jako takiego nie możemy go leczyć – leczenie działa tylko na sprzymierzeńców (interfejs Ally).
Na szczęście nasz druid ma metodę speakWithAnimals(Enemy enemy) która bierze tego złego niedźwiadka i adaptuje go na przyjaciela:
CODE
Ally speakWithAnimals(Enemy enemy){
System.out.println("Druid rzuca czar 'Rozmowa ze zwierzętami': " + enemy);
if (enemy instanceof BrownBear) {
System.out.println("[Sukces. Otrzymano nowego sojusznika.]");
return new EnemyToAllyAdapter(enemy);
}
return null;
}

I już wszystko działa jak trzeba – uszatek co prawda teraz strzela do własnej bramki, ale za to możemy go leczyć i miziać po brzuszku (funkcja do zaimplementowania w przyszłości).

ITCandidateEvaluator
Sytuacja wygląda dokładnie tak samo jak przy Dekoratorze. Biorąc pod uwagę, że projekt zaczynam od zera i robię go w pełni samodzielnie – wysoce wątpliwe jest abym musiał tworzyć jakiekolwiek adaptery. Jeśli jakiś interfejs będzie mi potrzebny to go po prostu zaimplementuję do danej klasy jeszcze w czasie jej tworzenia. I po kłopocie.
Wzorce dekorator i adapter mają najwięcej racji bytu przy operowaniu na starszym kodzie, a w dodatku nie naszym i pozbawionym testów. Wtedy jakakolwiek modyfikacja nawet najmniejszej metody to stąpanie po kruchym lodzie – nigdy nie wiemy co spieprzymy podmieniając choćby i jedną, z pozoru nic nie znaczącą zmienną.
Ja takiego problemu (na razie) nie mam dlatego zapraszam do kolejnego odcinka gdzie zajmiemy się wzorcem Fasada.