Źródło obrazka: Pixels.com (aut. Hans Jankowski)
Bohater dzisiejszego wpisu z jednej strony potrafi wyglądem przypominać Dekoratora. Z drugiej jego zachowaniu bliżej do wzorca Stan. Poznajcie: Łańcuch odpowiedzialności!
Wyobraźmy sobie mały bar w którym można się najeść i napić. Siedzi tam sobie klient i popija kapuczynę przeglądając telefon. W pewnej chwili próbując chwycić za filiżankę omyłkowo ją potrącił i wylał napój. Zauważył to sprzedawca i ruszył na miejsce z mopem.
Ale to nie wszystko. Klient zaczyna się awanturować, że utrata kawy to nie jego wina, że kubek był mokry i mu się wyślizgnął. Żąda nowej kapuczyny, a po odmowie ze strony sprzedawcy mówi, że chce się widzieć z kierownikiem.
Przychodzi wezwany kierownik i zaczyna się wymiana zdań. Klient krzyczy, a menedżer próbuje go uspokoić. W pewnym momencie dochodzi do gróźb, zaczyna się szamotanina. W tym miejscu do gry wchodzi policjant po służbie, który do tej pory tylko obserwował sytuację znad swojej herbaty.

Przytoczona historia obrazuje w jaki dokładnie sposób działa i kiedy się przydaje wzorzec Łańcuch. Domyślnie łączy on klasy z tym samym interfejsem i przepuszcza obiekt klienta przez ich kolejne metody. Czasami już pierwsza załatwi potrzebę obiektu, a czasem dopiero ta ostatnia (albo żadna).
Jeśli problemem byłaby tylko plama na podłodze, to wystarczył by sam sprzedawca z mopem, ale kiedy pojawiły się groźby i przemoc, potrzebna była policja.
Gdyby zależało wam na przykładach bardziej związanych z programowaniem mógłbym wymienić filtr antyspamowy na kontach mailowych. Kiedy przychodzi nowa poczta, najpierw sprawdza się czy to nie spam. Potem sprawdza się czy nadawca nie jest zapisany w naszych kontaktach – jeśli tak to mail leci do folderu Ważne. I tak dalej.

We wstępniaku mówiłem, że składnia Łańcucha wygląda trochę jak ta z Dekoratora, spójrzcie sami:
Mail mail = new Mail();
mail.setFilter(new SpamFilter(new RegularMail(new ImportantMail(null))));
Poczta przechodzi przez Spam, potem przez Zwykłe Wiadomości, potem przez Ważne. Na każdym z tych filtrów może się zatrzymać. Spójrzmy jaka jest oficjalna definicja Łańcucha:
Łańcuch odpowiedzialności – umożliwia dynamiczne dodawanie i usuwanie procedur obsługi poprzez zmianę elementów lub kolejności łańcucha. Ponadto upraszcza obiekt, ponieważ nie wymaga utrzymywania informacji o strukturze łańcucha – separuje nadawcę żądania od jego odbiorców.
Rusz głową! Wzorce projektowe
Jeśli zaś chodzi o strukturę to ważne aby każde z ogniw Łańcucha posiadało informację o jego następcy – do kogo się zwrócić jeśli samemu nie uda się sprostać potrzebom obiektu. Wyglądem kodu przypomina to nieco wzorzec Stan i jego odniesienia do kolejnych stanów. Pamiętajmy jednak, że „stany” to hermetyzowane zachowania jednego obiektu, natomiast w Łańcuchu mamy metody różnych klas przez które ma przejść obiekt (albo się na którejś zatrzymać).
Ale po kolei…
To co lubię w grach cRPG to uczucie zagubienia i swojej maleńkości wobec wrogów na początku rozgrywki. Potem poznajemy ten świat, zdobywamy doświadczenie i podchodzimy do coraz to trudniejszych wyzwań. Jeśli nie zaniedbaliśmy rozwoju, to do końcowego bossa wkraczamy bez jakiejkolwiek spiny i tylko jak już jest po wszystkim przypominamy sobie o całej torbie mikstur siły, niewrażliwości i szybkości jakie chomikowaliśmy na czarną godzinę, która nigdy nie nadeszła.
Równocześnie nienawidzę, kiedy gra wymusza skalowanie przeciwników do naszego poziomu. Niby, że dla balansu rozgrywki, ale chrzanię taką sytuację, kiedy zwykły szczur nagle ma 200 punktów życia, tylko dlatego bo podekspiliśmy. Już wolę żeby było jak w Gothicu – w czasie wypełniania ostatnich questów biegałem przez mapę jak szalony i tylko od niechcenia rozpłatywałem ścigające mnie Zębacze – potwory, które brały mnie na hita przez pierwsze godziny zabawy.

W takich realiach naturalnym jest, że czasami zawędrujemy za daleko i natrafimy na przeciwnika, który położy nas jednym machnięciem łapy. Wtedy korzystamy z metody loadGame() i już wiemy z kim nie należy zadzierać… póki nie podniesiemy poziomu i nie kupimy lepszego miecza.
Na bazie powyższego stworzyłem kawałek kodu (tutaj), który pokazuje jak owa sytuacja wyglądałaby ze wzorcem Łańcuch:

Widzimy, że nasz barbarzyńca z pierwszym poziomie nie stanowi wyzwania nawet dla Goblina, ale z kolejnymi levelami udaje mu się pokonywać coraz to mocniejszych wrogów, aż na placu boju pozostał tylko on. Zwycięstwo!
public void defend(Barbarian barbarian) {
if (barbarian.level <= 3) System.out.println("Goblin: Dam mu radę sam!");
else {
System.out.println("Goblin: Potrzebuję pomocy!");
barbarian.setEnemy(nextEnemy);
}
}
Powyżej widzimy metodę Goblina, która sprawdza czy samemu da radę obiektowi barbarzyńcy, a jeśli nie to wysyła go do nextEnemy wstrzykniętego wcześniej do swojego konstruktora.
Tak wygląda to w dynamicznej implementacji, kiedy kolejne ogniwa wybierane są w czasie tworzenia łańcucha:
Barbarian barbarianLv1 = new Barbarian(1);
barbarianLv1.setEnemy(new Goblin(new OrcWarlord(new TrollKing(null))));
Możemy też nieco usztywnić architekturę, tak jak to zrobiłem w drugiej, rozbudowanej wersji kodu (tutaj). Wtedy następny przeciwnik jest już na stałe ustawiony u poprzednika:



ITCandidateEvaluator
Bez skruchy przyznaję, że wzorzec Chain of Responsibility wybrałem do opisania właśnie dlatego bo miałem fajny pomysł na jego zakodowanie z udziałem Króla Trolli i jego przydupasów. Pisanie tego o ósmej rano, ze stygnącą kawą obok monitora dało mi sporo frajdy – nawet kiedy Chat GPT ocenił to na słabą próbę podejścia do wzorca (dlatego powstała jeszcze krótsza, bardziej „książkowa” wersja kodu).
Mimo osobistej sympatii do Łańcucha nie widzę jednak wiele możliwości umieszczenia go u siebie w kolejnym projekcie. Niby genialnie nadałby się do logowania wyjątków o różnych poziomach ważności, ale po co będę sam to pisał, skoro logger log4j zrobi to za mnie?
Tyle w temacie wzorców prostych. W kolejnym odcinku przerobimy jeszcze Model-View-Controller i chwilowo dajemy sobie na wstrzymanie z tą serią.