Źródło obrazka: zmieniona okładka książki "Wielki Brat" George'a Orwella
Obserwator to jeden z najpopularniejszych wzorców projektowych. Tak wszechobecny, że niejednokrotnie wykorzystujemy jego zasady sami o tym nie wiedząc. Dzisiaj nauczymy się jak stosować go „by the books”.
Z observerem miałem po raz pierwszy do czynienia gdzieś pod koniec 30 poziomu na CodeGym. Było tam duże zadanie w którym tworzyliśmy symulację pracy restauracji – klasa typu fabryka tworzy nam randomowo zamówienia, po czym trafiają one do kucharzy którzy spędzają x czasu na ich przygotowaniu. Następnie te wirtualne potrawy są zabierane przez kelnera do wyznaczonego stolika.
Sięgając do definicji Obserwatora otrzymamy:
Definiuje pomiędzy obiektami relację jeden-do-wielu w taki sposób, że kiedy jeden obiekt zmienia swój stan, wszystkie jego obiekty zależne zostają o tym powiadomione i automatycznie zaktualizowane.
Rusz głową! Wzorce projektowe.
W wyżej przytoczonym zadaniu takim obiektem zmieniającym stan był Kucharz przygotowujący posiłki. Miał dwa stany: „wolny” i „zajęty”. Po upłynięciu iluśtam milisekund „spędzonych na gotowaniu”, zmienna „busy” otrzymywała wartość false o czym natychmiast zostawał poinformowany Kelner czyli nasz obserwator.
Jeszcze do wczoraj myślałem, że to był jedyny przypadek użycia Obserwatora przeze mnie. Byłem w błędzie. Dużym.

Otóż moi drodzy, cały frontend bazuje na tych czy innych implementacjach Observera.
Za każdym razem kiedy ustawiałem Mouse/Keyboard listener albo ActionListener przy JComponentach Swinga, tudzież EventListener do stworzenia klasy kontrolera w strukturze MVC – nieświadomie korzystałem z wzorca o którym dzisiaj mówimy.
Tym razem będzie inaczej – poniższy kod został przemyślany i napisany z pełną premedytacją!
Ale po kolei…
Najpotężniejszą klasą jaką kiedykolwiek przyszło mi grać we Wrota Baldura 2 jest bezsprzecznie dwuklasowiec Wojownik/Mag. Specjalnie nawiązuję do drugiej części gry, bo tam już na starcie mamy 8-9 poziom doświadczenia. Tworząc taką postać jako wojownika otrzymujemy sporą pulę punktów życia, potrafimy posługiwać się orężem i unikać ciosów wroga. Teraz możemy wybrać drugą klasę jako maga, relatywnie szybko ją „podekspić” i dostać dodatkowe benefity. Tworzymy w ten sposób bitewnego czarodzieja – śmiertelnie groźnego w zwarciu i na dystans, zdolnego przebić magiczne osłony przeciwnika i szybko zregenerować własne.
Niesamowicie przydatnym czarem już na początku jest „Kamienna skóra”. Mag rzuca ją na siebie i otrzymuje kilka warstw owej skóry. Teraz gdy przeciwnik w niego trafi, zamiast utraty punktów życia zdejmuje mu się jedna warstwa KS. Dopiero kiedy wszystkie warstwy znikną, wróg może dobrać się do „mięsa”.

Ale tylko teoretycznie. Gdyż z Kamienną skórą genialnie komponuje się zaklęcie „Warunkowanie” czyli znany z programowania „if”. Rzucając je wybieramy kiedy ma się uaktywnić i jaki inny czar będzie jego efektem. Możemy zatem śmiało stworzyć warunek:
Jeśli otrzymam jakiekolwiek obrażenia, rzuć na mnie Kamienną Skórę.

Podsumujmy:
Nie dość, że jesteśmy dobrze wyszkolonym wojownikiem którego trudno trafić, nie dość, że już na początku walki mamy ze 6 warstw kamiennej skóry, to jeszcze jak przeciwnik je zdejmie to się automatycznie odnawiają. A KS to tylko jeden z wielu czarów ochronnych jakimi dysponują czarodzieje z DnD.
Teraz spójrzcie jak taka walka wyglądałaby w kodzie z użyciem Obserwatora. Pełny kod tutaj.
public static void main(String[] args) {
Wizard wizard = new Wizard();
Orc orc = new Orc();
wizard.addObserver(new Cleric());
wizard.addObserver(new Contingency());
orc.meleeAttack(wizard);
orc.meleeAttack(wizard);
orc.meleeAttack(wizard);
}
I efekt potyczki:

Jak widzicie nie wygląda to na nic zbyt skomplikowanego, ale żeby dobrze zrozumieć co się dzieje pod maską musimy porównać powyższy przykład z poniższym, gdzie zakomentowałem obserwatorów.

Nikt naszego biednego czarodzieja już nie osłania. Ork jeździ po nim jak po burej kobyle. Pozostało już tylko mieć nadzieję, że kleryk ma przygotowany czar wskrzeszenia.
Dobrze, a teraz sprawdźmy resztę kodu i zobaczmy co tam się dzieje:

static class Character extends Observable{
int hp = 10;
void takeDamage(int damage){
hp -= damage;
System.out.println(hp);
}
}
static class Wizard extends Character
(...)
Przede wszystkim nasze postaci: Mag, Kleryk, Ork dziedziczą po klasie Character co sprawia, że mogą się nawzajem atakować i leczyć. Natomiast sama klasa Character dziedziczy po Observable – klasie wyposażonej w takie metody jak addObserver() do dodania obserwatorów, setChanged() do zarejestrowania zmian oraz notifyObservers() do powiadomienia obserwatorów, że coś się zmieniło:
System.out.println("Czarodziej: Jestem ranny! Zostało mi tylko " + hp + "hp!");
setChanged();
notifyObservers();
Jak tylko czarodziej otrzyma jakiekolwiek rany zawiadamia o zmianie stanu (setChanged) po czym idzie z tym do „swoich ludzi”.
Jeżeli chodzi o samych obserwatorów to muszą oni implementować interfejs Observer, który zawiera metodę update(). Jest ona automatycznie wywoływana tuż po wysłaniu powiadomienia. U mnie pierwszym odbiorcą owego powiadomienia jest kleryk:
@Override
public void update(Observable o, Object arg) {
Character character;
if (o instanceof Character) {
character = (Character) o;
heal(character);
}
}
Jak tylko kapłan dowiaduje się, że mag jest ranny – bierze się za leczenie.
Drugim obserwatorem jest czar Warunkowanie, który również uaktywnia się kiedy mag straci jakieś ha-pe-ki:
@Override
public void update(Observable o, Object arg) {
Character character;
if (o instanceof Character) {
character = (Character) o;
castSpell(character);
}
}
Tutaj efektem otrzymania powiadomienia jest rzucenie nowej Kamiennej skóry, która chroni przed kolejnymi atakami ze strony orka.
ITCandidateEvaluator
Myślę, że już wiecie jak to wszystko działa. Tłumacząc zawiłości ja też miałem okazję sporo pomyśleć nad tematem i zastanowić się czy Observer będzie miał jakiekolwiek zastosowanie w moim drugim projekcie.
Wniosek – jak najbardziej. Znowu skorzystam z bibliotek Swinga do zwizualizowania swojego pomysłu. Ponadto jeszcze raz użyję architektury MVC z kontrolerem czekającym na jakiekolwiek sygnały ze strony widoku.
Coś czuję, że z obserwatorem zakumpluję się na dłużej, aczkolwiek niekoniecznie w wersji z bibliotek java.util. Sposób, który tutaj przedstawiłem jest już przestarzały – kłóci się z jedną z zasad poprawnej architektury oprogramowania:
„Przedkładaj kompozycję ponad dziedziczenie.„
Dziedziczenie jest super, ale to bardzo potężna broń, która ma jeden ładunek (na raz można dziedziczyć tylko po jednej klasie) dlatego jak z atomówką – w swoich projektach używajmy go w ostateczności, tam gdzie to faktycznie niezbędne.
Dziękuję za uwagę i zapraszam do następnego odcinka – z wzorcem „Dekorator”.