Wzorzec metody szablonowej definiuje szkielet algorytmu w klasie bazowej i pozwala podklasom zastępować wybrane kroki bez zmiany struktury algorytmu.
Cel
Wzorzec daje elastyczny sposób dostosowania implementacji bez zmiany ogólnego przepływu. Przydaje się, gdy masz kilka wariantów algorytmu dzielących wspólną logikę.
Problem
Algorytm ma stałą kolejność kroków, ale kilka z nich różni się między implementacjami. Dziedziczenie potrafi zduplikować wspólną część, a ścisłe powiązanie utrudnia zmiany. Metoda szablonowa rozwiązuje to tak:
- Zachowuje strukturę algorytmu: ogólny przepływ jest ten sam w każdej implementacji.
- Zmniejsza duplikację: wspólna logika trafia raz do klasy bazowej.
- Pozwala dostosować tylko to, co się różni.
- Dopuszcza kompozycję: zamiast dziedziczenia możesz przekazać funkcje lub funktory.
Rozwiązanie
Zdefiniuj abstrakcyjną klasę bazową (albo interfejs) z metodą szablonową, która opisuje kroki algorytmu, oraz z metodami abstrakcyjnymi lub punktami zaczepienia dla kroków, które mają się różnić. Konkretne klasy implementują te kroki.
Nowoczesne podejście stawia na kompozycję: funkcje zwrotne (std::function w C++, callables w Pythonie) przekazujesz przez konstruktor. Wzorzec bywa wtedy elastyczniejszy niż przy tradycyjnym dziedziczeniu.
Główni uczestnicy
- Klasa abstrakcyjna: definiuje metodę szablonową i dostosowywalne kroki.
- Klasa konkretna: implementuje dostosowywalne kroki.
- Konsument algorytmu: używa klasy i może dostarczyć własne implementacje.
Zalety
- Zmniejsza duplikację kodu, bo wspólna logika trafia w jedno miejsce.
- Czyni strukturę algorytmu jawną i pozwala użyć jej wielokrotnie.
- Dzieli odpowiedzialności w czytelny sposób.
- Działa zarówno z dziedziczeniem, jak i z kompozycją.
- Pozwala testować poszczególne kroki w izolacji.
Wady
- Wprowadza zbędną klasę bazową, gdy implementacja jest tylko jedna.
- Złożone hierarchie algorytmów trudno utrzymać.
- Klient musi znać kroki algorytmu i punkty zaczepienia.
Podejścia do implementacji
Podejście 1: tradycyjne dziedziczenie
W klasie bazowej zdefiniuj metody abstrakcyjne, które podklasy muszą zaimplementować.
Podejście 2: kompozycja z funkcjami zwrotnymi
Przekaż funkcje zwrotne albo obiekty funkcji przez konstruktor. Unikasz wtedy hierarchii dziedziczenia, a wzorzec staje się elastyczniejszy.
Struktura wzorca
Diagram klas UML pokazuje strukturę tradycyjnego wariantu:
Kluczowe elementy:
- AbstractTemplate: zawiera metodę szablonową, która organizuje kroki algorytmu.
- primitiveOperation1-3: metody abstrakcyjne (dostosowywalne kroki), które podklasy muszą zaimplementować.
- ConcreteImplementationA/B: dostarczają konkretne implementacje metod abstrakcyjnych.
- Struktura algorytmu jest ustalona w klasie bazowej; zmienia się tylko szczegół.
Uwaga: nowoczesne implementacje często zamiast dziedziczenia przekazują funkcje. Wzorzec staje się przez to elastyczniejszy, ale jego idea się nie zmienia.
Podsumowanie
Wzorzec jest mały. Jedna niewirtualna funkcja ustala kolejność, a funkcje wirtualne albo obiekty wywoływalne wypełniają kroki. Użyj dziedziczenia, gdy kroki należą do typu. Użyj obiektów wywoływalnych, gdy wywołujący ma podmieniać zachowanie bez tworzenia podklasy.
Przykłady
Implementacje w różnych językach: