Obrazek wygenerowany przez AI (Bing AI)
Historia lubi się powtarzać. Kiedy w ostatnim artykule opisywałem wam istotę wzorca „Obserwator”, byłem zaskoczony tym jak często z niego korzystałem nawet o tym nie wiedząc. Otwierając nowy rozdział Rusz Głową! i przechodząc do „Dekoratora” pomyślałem – „w końcu coś świeżego!”. Cóż…
Każda część w/w książki rozpoczyna się nakreśleniem tła fabularnego – przytacza się jakiś zagmatwany problem, a parę kartek dalej wchodzi omawiany wzorzec (cały na biało) i ratuje sytuację kilkoma celnie strzelonymi linijkami kodu.
I tak sobie czytam o kawiarence, która wpadła w tarapaty bo oferowała zbyt wiele różnych kaw do wyboru, a teraz programiści muszą tworzyć kolejne podklasy i podklasy podklas, a kod się duplikuje i dzieci płaczą. Sam się głowię – co teraz?! Gdzie ten Dekorator?!
Pojawiają się pierwsze linijki kodu i powoli do mnie dociera:
Hej, ja to już gdzieś widziałem!
A potem olśnienie i fala wstydu, bowiem tak właściwie to od Dekoratora zaczęła się moja przygoda z Javą! Nie zliczę ile razy wpisałem poniższą linijkę kodu rozwiązując zadania na CodeGym:
BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
Oficjalna definicja wzorca brzmi następująco:
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
Ach, dziedziczenie. Junior powiedziałby, że to lek na wszelkie bolączki. Senior: „sprawca mego upodlenia, obiekt mojej zemsty”.

Poprzedni wpis zakończyłem poradą:
„Przedkładaj kompozycję ponad dziedziczenie.”
Nie są to puste słowa. Unikanie dziedziczenia pozwala tworzyć bardziej elstyczny kod, odporny na działanie czasu i łatwiejszy w testowaniu. Musimy jednakowoż pamiętać, że superklasy i podklasy znajdują się w Javie nie bez powodu. Używanie ich z głową potrafi uprościć kod (chociaż kosztem jego „usztywnienia”). Dobry programista już na etapie projektowania zauważy moduły gdzie w przyszłości mogą pojawić się zmiany (czyli gdzie dziedziczenia należy unikać) oraz takie, które raczej będą stałe przez cały cykl rozwoju aplikacji. To tam właśnie nie ma co kombinować i można pozwolić sobie na kilka podklas.
Wzorzec „Dekorator” w tym wszystkim jest rozwiązaniem iście uniwersalnym – sprawdzi się zarówno przy zawiłej klasowej strukturze tzw. legacy code oraz tam gdzie zaczynamy od zera i już na starcie chcemy zadbać o porządną kompozycję.
Ale po kolei…
Najnudniejszą klasą w grach opartych na DnD jest według mnie bezapelacyjnie wojownik. Co oczywiście nie oznacza, że jest słaby, o nie! Dobrze wyszkolony fajter nie da sobie w kaszę dmuchać nawet wysokopoziomowym magom – spora pula punktów życia pozwala mu przetrwać więcej kul ognia niż przeciętny czarodziej potrafi wyciągnąć ze swojego rękawa. Z kolei świetne modyfikatory do rzutów obronnych sprawią, że nawet cała banda czarowników może mu wygrażać „Palcami Śmierci”, a i tak szansa na natychmiastowy zgon będzie minimalna.
Problem w tym, że gra wojownikiem jest zwyczajnie nudna. Każda potyczka wygląda tak samo:
- kliknięcie na wroga i atak
- jak pierwszy wróg padnie, kliknij na drugiego i atak
- zapętl póki jeszcze jacyś wrogowie żyją
Szczególnie w porównaniu do czarodzieja, który raz, że musi przygotować zestaw czarów na daną okazję (inny na orków, inny na wampiry, inny na smoka) to jeszcze musi nimi żonglować w czasie potyczki kontrując to co robi przeciwnik.
- wrogi mag rzuca: „Kamienna skóra”
- ty rzucasz „Wyłom”, który ją zdejmuje
- przydupasy gobliny biegną w twoją stronę
- ty rzucasz „Kulę ognia” w sam środek grupy
- wrogi mag rzuca „Chaotyczne rozkazy” i otumania twojego kolegę
- ty strzelasz „Rozproszeniem magii”
- …
I tak dalej. Dzieje się. Jest akcja.
Pewnym rozwiązaniem, które czyni prowadzenie wojownika ciekawszym jest dobór odpowiednich artefaktów czyli magicznych przedmiotów, które dysponują zaklęciami czarodzieja, tak, że nawet zwykły rębajło może z nich skorzystać.
Przykładami takich błyskotek są np. pierścień niewidzialności, buty szybkości lub płaszcz ochrony przed pociskami. Funkcji nie będę tłumaczył, bo nazwy mówią same za siebie. Pytanie tylko – jak to zakodować?
Jak (już w czasie działania programu) sprawić, żeby nasz wojownik, który nie potrafi czarować – nagle nabył tę umiejętność?
Spójrzmy na przykład. Cały kod znajdziecie tutaj.

Wojownik próbuje rzucić zaklęcie, ale nic się nie dzieje. Kod nie został zaimplementowany, bo i po co – WOJOWNICY NIE CZARUJĄ!
Ale, ale nic straconego. Wystarczy bowiem opakować (lub właśnie „udekorować„) obiekt wojownika w obiekt pierścienia niewidzialności i cyk! Nagle nasz woj zna magię!

Co ważne, takie opakowywanie nie musi być wcale jednokrotne. Równie dobrze możemy zrobić tak:

Podsumowując. Konstrukcja Dekoratora jest całkiem prosta – mamy obiekt dekorowany i obiekty dekorujące, które w swoich konstruktorach muszą mieć parametr tożsamy z obiektem dekorowanym (ta sama klasa lub interfejs). W moim przypadku był to interfejs Spellcaster:
interface Spellcaster{
void castSpell();
}
static class Warrior implements Spellcaster{
@Override
public void castSpell() {
System.out.println("Wojownik rzuca czar:");
}
}
static class InvisibilityRing implements Spellcaster{
Spellcaster spellcaster;
public InvisibilityRing(Spellcaster spellcaster) {
this.spellcaster = spellcaster;
}
@Override
public void castSpell() {
spellcaster.castSpell();
System.out.println("Niewidzialność.");
}
}
Czarownikiem jest zatem nawet sam pierścień, zauważcie jednak, że nie skorzystamy z niego jeśli nie założymy go na palec wojownika, który jest końcowym elementem układanki (tylko jego konstruktor nie wymaga dodatkowych parametrów).
ITCandidateEvaluator
Biorąc pod uwagę jak powszechny jest Dekorator nie mam absolutnie żadnych wątpliwości, że posłużę się nim w moim kolejnym projekcie. Na pewno przydarzy się to przy korzystaniu ze standardowych bibliotek Javy, np. podczas pracy z plikami:
BufferedInputStream inputStream = new BufferedInputStream(new FileInputStream(new File(...)));
Jeśli z kolei spytacie się mnie czy zamierzam tworzyć własne dekoratory to wydaje mi się, że nie zajdzie taka potrzeba. Mam tę komfortową sytuację, że program będę pisał „od zera”, więc moje klasy i ich obiekty już na starcie mogą mieć wszystkie wymagane metody.
Tym niemniej, będę pamiętał o dekoratorze i jeśli tylko pomoże mi on uniknąć niepotrzebnego dziedziczenia to nie zawaham się go użyć.
Dziękuję za uwagę i zapraszam do następnego odcinka – z wzorcem „Fabryka”.