Zdziwilibyście się wiedząc jak często używana jest metoda szablonowa
Opisując wzorzec Polecenie (Command) zdarzyło mi się napomknąć, że „to taka ulepszona Strategia” – trochę bardziej skomplikowana, za to z dodatkowymi funkcjami. Idąc tym tokiem rozumowania, bohater dzisiejszego odcinka: Metoda szablonowa jest niczym więcej aniżeli połową Strategii.
Przypomnijmy: wzorzec Strategy pozwala nam np. tworzyć różne sposoby pozyskania danych. Jeśli tworzymy sobie taki agregator wyszukujący wszystkie oferty pracy na rynku to musi on korzystać z różnych stron pośredniczących: nofluffjobs, pracuj.pl, justjoin.it i tak dalej. Każdy z tych portali posługuje się innym API i musimy się do niego dostosować, aby dostać to co chcemy – każdy wymaga swojej własnej strategii. Nasz agregator łączy się najpierw z NoFluff używając do tego klasy o przykładowej nazwie NoFluffStrategy, potem korzysta z klasy PracujPlStrategy i tak, dalej aż w końcu wypluwa nam pełną listę wyszukanych ofert.
Wszystko wydaje się działać doskonale, czy więc ten mechanizm można jakoś ulepszyć?
Nie wiem, czy słyszeliście o pojęciu „decoupling” czyli usuwaniu duplikacji kodu. Ogólnie przyjętą zasadą i dobrym stylem programowania jest unikanie powtarzających się linijek kodu – eksportowanie ich do osobnej klasy/metody. Ta reguła ma wiele nazw, ale dla ułatwienia zapiszmy ją w taki sposób:
Przedkładaj ponowne wykorzystywanie kodu ponad jego duplikację.
Jeśli już programujecie to z pewnością zdajecie sobie sprawę jak upierdliwe są metody przyjmujące za parametr wartość String. Prawie za każdym razem, jeszcze zanim zaczniemy przetwarzać otrzymany tekst, musimy wpierw sprawdzić czy String nie jest null, tudzież nie jest pusty:
if (text == null
|| text.length == 0)
throw new InvalidArgumentException;
W końcu pojawiła się grupa programistów, która powiedziała „DOŚĆ!” i tak powstały biblioteki Apache Commons będące po prostu ulepszonymi standardowymi bibliotekami Javy, gdzie już nie musimy sprawdzać każdego Stringa (i wielu innych obiektów, framework jest większy niż myślicie) z osobna. Wszystko dzieje się w tle. Doskonały przykład decouplingu.

Wracając jednak do przykładu z agregatorem – z pewnością zauważycie, że w każdej strategii wiele elementów jest podobnych. A ostatecznie może wyjść tak, że jedna od drugiej różni się tylko wykorzystanym regexem oraz sposobem logowania. Aby usunąć tak powielony kod, a jednocześnie zachować pełnię kontroli nad algorytmem i utrzymać odpowiednią hermetyzację możemy skorzystać z Metody Szablonowej:
Wzorzec Metoda Szablonowa definiuje szkielet danego algorytmu w określonej metodzie, przekazując realizację niektórych jego kroków do klas podrzędnych. Wzorzec ten pozwala klasom podrzędnym na redefiniowanie pewnych kroków algorytmu, ale jednocześnie uniemożliwia zmianę jego struktury.
Rusz głową! Wzorce projektowe
Teraz, skoro już wiemy do czego służy, pozostaje nam tylko nauczyć się jak z tego wzorca korzystać.
Ale po kolei…
Ostatnim razem, przy okazji zabawy z Fasadą, pokazywałem wam jak wyglądają przygotowania do walki z perspektywy całej, choć początkującej drużyny. Czarodziej rzuca przyśpieszenie ruchów na wszystkich, Druid przyzywa zwierzaki, Łotrzyk ukrywa się w cieniu i tak dalej.
Są to raczej standardowe mechanizmy, które powielamy przed każdą potyczką. Czasami jednak musimy dorzucić coś ekstra aby skontrować skille konkretnego przeciwnika. Weźmy za przykład wampira.

W DnD wampir owszem jest trudnym zawodnikiem – ma niemało punktów życia, trudno go trafić, zadaje przyzwoite obrażenia… podobnie jak cała masa innych wrogów. To co czyni walkę z nim śmiertelnie niebezpieczną jest jego umiejętność wysysania poziomów.
Czyli np. my walczymy 12-poziomowym kapłanem, ale już jedno trafienie przez wampira zabiera nam 3 poziomy doświadczenia, a co za tym idzie zapominamy wartościowe czary, spadają nam maksymalne punkty zdrowia, zmniejsza się szansa na trafienie. Kolejne trzy udane ataki krwiopijcy i bohater ginie. Bez możliwości wskrzeszenia. Pozamiatane.
Jedynym sposobem by poradzić sobie z wampirami w walce bezpośredniej jest nałożenie na bohatera, który jest atakowany, czaru „Ochrona przed negatywną energią” który uniemożliwia wysysanie poziomów. Problem jednak jest taki, że to zaklęcie ma stosunkowo krótki czas trwania, dlatego rzucamy je tuż przed starciem.
A jak to wygląda w kodzie? Pełna wersja tutaj.
Oto nasza Metoda Szablonowa zaimplementowana w klasie abstrakcyjnej:
public final void prepareForBattle(){
macroName(); //abstract
summon();
blessing();
customDefensiveBuff(); //abstract
extraSpell();
}
Wywołuje ona 5 innych metod w Z GÓRY USTALONYM PORZĄDKU. Sekwencja jest kluczowa – najpierw przyzywamy summony, żeby załapały się na grupowe błogosławieństwo. Wspomniany wyżej czar ochrony przed negatywną energią rzucamy za to pod koniec makra, tak by starczył na jak najdłuższy czas.

Tutaj widzicie co i dlaczego. Metoda prepareForBattle() jest zaimplementowana w klasie bazowej i ustawiona na finalną (final) tak, by podklasy nie mogły jej przesłaniać i kombinować z sekwencją zdarzeń.
Metoda summon() też jest zaimplementowana w super-klasie, ale może być przesłaniana. Co prawda przyzwanie powietrznego sługi sprawdza się w większości przypadków, ale czasem jest zbędne (bo np. nasze poprzednie summony wciąż żyją). Podobnie z błogosławieństwem – to podstawowy czar, jeśli dysponujemy np. Psalmem to lepiej przesłonić metodę.
customDefensiveBuff() jest metodą abstrakcyjną, czyli nasza podklasa jest ZOBOWIĄZANA ją zaimplementować. Będzie inna dla walki z nieumarłymi, a inna przy bitwie z czarnoksiężnikiem.
Na koniec zostaje nam extraSpell(), który jest już niby zaimplementowany w klasie bazowej, ale jest pusty. Dlaczego tak? Ponieważ jest nieobowiązkowy. Użytkownik ma możliwość dołożyć coś nowego do algorytmu, ale nie musi. Takie coś w świecie kodu nosi nazwę „haczyk”. Haczyków może nie być wcale, a może być wiele – nic nas nie ogranicza.

A na koniec pokaz możliwości każdej „strategii”. Wszystko działa jak należy, a do tego udało się uniknąć duplikacji kodu. Dziedziczenie użyte zgodnie z przeznaczeniem.
ITCandidateEvaluator
Z metody szablonowej jeszcze nie korzystałem, ale teraz – po poznaniu jej zasad oraz zalet – jestem pewien, że to się zmieni.
Już mówiłem, że w moim kolejnym projekcie pojawi się z góry określona hierarchia dziedziczenia. Na „górze” będzie abstrakcyjna klasa Kandydat, będą podklasy Junior, Mid, Senior, może coś jeszcze. Pewne elementy tych klas będą unikalne dla każdej z nich, ale już wiem, że jedna część będzie wspólna dla wszystkich: algorytm wyliczający końcowy wynik.
Nie będzie działał na zasadzie prostej średniej z punktów za poszczególne etapy, wzór będzie trochę bardziej skomplikowany. I może nieco inny dla każdej podklasy.
Wygląda to na wprost idealne miejsce na zastosowanie metody szablonowej – zamieszczę ją w abstrakcyjnej klasie Candidate, a następnie wykorzystam i uzupełnię poszczególne składowe w klasach dziedziczących. Do tej pory uważałem, że sprawę załatwię wzorcem Strategy, ale teraz widzę, że pojawiła się znacznie bardziej stylowa opcja.
Myślę, że na dziś wystarczy i jak zwykle dziękuję za poświęcony czas. Zapraszam do kolejnego odcinka gdzie zajmiemy się wzorcem „Iterator„.