Wzorzec Template Method pomaga wtedy, gdy kilka klas ma wspólny przebieg pracy, ale różni się szczegółami na wybranych etapach. Pokażę, jak rozpoznać taki przypadek, jak wygląda układ klas, gdzie ten wzorzec naprawdę oszczędza kod i kiedy lepiej wybrać prostsze rozwiązanie.
Najważniejsze informacje, które warto mieć na starcie
- Metoda szablonowa zamraża kolejność kroków, a podklasom oddaje tylko te miejsca, które mają się różnić.
- Najlepiej działa wtedy, gdy masz jeden stabilny proces i tylko 1-3 zmienne kroki.
- W praktyce opiera się na klasie bazowej, metodach abstrakcyjnych i opcjonalnych hookach.
- To dobre rozwiązanie dla powtarzalnych workflow, np. importu danych, renderowania lub obsługi żądania.
- Jeśli większość algorytmu i tak zmienia się między wariantami, lepiej sprawdza się kompozycja albo Strategy.
Na czym polega wzorzec szablonowej metody
Ja patrzę na ten wzorzec przede wszystkim jak na sposób uporządkowania procesu. Klasa bazowa opisuje szkielet algorytmu, czyli kolejność kroków, a klasy potomne wypełniają tylko te fragmenty, które mogą się różnić. Dzięki temu cały przebieg pozostaje spójny, a kod nie rozjeżdża się na kilka wersji tej samej logiki.
To ważne rozróżnienie: nie chodzi tu o zwykłe dziedziczenie dla samego dziedziczenia. Chodzi o kontrolę nad tym, co ma zostać wykonane i w jakiej kolejności. Jeśli jeden z etapów musi zawsze nastąpić przed innym, a jedynie jego treść bywa różna, ten wzorzec pasuje naturalnie. To przykład odwrócenia sterowania: klasa bazowa prowadzi proces, a podklasy dostarczają szczegóły.
- Stałe elementy zostają w klasie bazowej.
- Zmienne kroki trafiają do metod abstrakcyjnych albo nadpisywanych.
- Hooki pozwalają opcjonalnie rozszerzyć zachowanie bez burzenia całego przepływu.
W praktyce taki układ jest szczególnie wygodny wtedy, gdy kilka różnych klas realizuje tę samą operację, ale inaczej rozumie jej wybrane fragmenty. Za chwilę rozłożę to na części pierwsze na prostym przykładzie z webowego świata.

Jak wygląda układ klas i przepływ wywołań
Najprostszy model składa się z jednej klasy bazowej i kilku klas potomnych. W klasie bazowej znajduje się metoda, którą wywołuje klient. To ona ustala kolejność działań. W środku wywołuje kolejne kroki pomocnicze, z których część ma implementację wspólną, a część jest zostawiona do uzupełnienia przez podklasy.
W językach takich jak Java czy C# taki punkt wejścia często oznacza się jako metodę, której nie powinno się nadpisywać. W Javie można to wymusić przez `final`, w C# przez odpowiednie ograniczenia klasy lub metody. W TypeScript i JavaScript zwykle opierasz się bardziej na konwencji, testach i dobrej strukturze kodu niż na twardej blokadzie kompilatora.
- Metoda szablonowa definiuje przebieg całej operacji.
- Metody abstrakcyjne wymuszają implementację kroków, które muszą się różnić.
- Metody z domyślną implementacją trzymają wspólną logikę w jednym miejscu.
- Hooki dają podklasom punkt zaczepienia, ale nie zmuszają do zmian.
W praktyce ten układ dobrze widać tam, gdzie framework narzuca cykl życia obiektu, a twoja klasa tylko wypełnia wyznaczone miejsca. To prowadzi do najciekawszej części: przykładu, który da się od razu przełożyć na realny projekt.
Przykład z aplikacji webowej
Załóżmy, że budujesz panel administracyjny, który importuje dane z różnych formatów. Wspólny przebieg jest ten sam: wczytaj plik, sparsuj zawartość, zwaliduj rekordy, zapisz je do bazy i wyślij wynik operacji. Różnić ma się tylko sposób parsowania i reguły walidacji. To bardzo dobry kandydat na ten wzorzec.
abstract class ImportJob {
public async run(file) {
const raw = await this.read(file);
const rows = this.parse(raw);
this.validate(rows);
await this.save(rows);
await this.afterSuccess(rows);
}
protected async read(file) {
// wspólny krok: pobranie danych
}
protected abstract parse(raw);
protected validate(rows) {
if (!rows.length) {
throw new Error("Brak danych do importu");
}
}
protected async save(rows) {
// zapis do bazy lub API
}
protected async afterSuccess(rows) {
// hook: np. log, mail, metryka
}
}
class CsvImportJob extends ImportJob {
protected parse(raw) {
// parsowanie CSV
}
}
Tu najważniejsze jest to, że run() pilnuje kolejności. Podklasa nie zmienia przebiegu importu, tylko podsuwa własny sposób interpretacji danych. Dzięki temu nie powielasz czterech podobnych metod w kilku klasach i nie ryzykujesz, że jedna z nich ominie walidację albo zapisze dane w złej kolejności.
Kiedy ten wzorzec naprawdę pomaga
Ja sięgam po niego wtedy, gdy widzę jeden stabilny szkielet i kilka lokalnych wariantów. Jeśli różnice między klasami dotyczą tylko 1-3 kroków, a reszta procesu jest identyczna, metoda szablonowa zwykle upraszcza projekt. Daje też coś, co bywa niedoceniane: czytelny standard przepływu. Każdy, kto otworzy klasę bazową, od razu widzi, co dzieje się najpierw, a co później.
- Gdy proces ma powtarzalny przebieg, ale różne źródła danych lub różne formaty wyjścia.
- Gdy chcesz wymusić wspólną kolejność kroków, np. walidację przed zapisem.
- Gdy framework lub warstwa aplikacji narzuca cykl życia obiektu.
- Gdy zależy ci na ograniczeniu duplikacji logiki wokół błędów, transakcji i metryk.
W takich sytuacjach wzorzec działa nie dlatego, że jest elegancki na papierze, tylko dlatego, że usuwa chaos z kodu produkcyjnego. Problem zaczyna się dopiero wtedy, gdy różnice między wariantami są większe niż wspólny szkielet.
Kiedy lepiej wybrać inne rozwiązanie
Jeśli każda nowa odmiana procesu wymaga nadpisania większości metod, szkielet przestaje dawać wartość. To moment, w którym dziedziczenie zaczyna być ciężarem. W praktyce widziałem projekty, w których podklasy nadpisywały około 80% kroków. W takim układzie bardziej opłaca się kompozycja albo Strategy, bo kod robi się mniej przewidywalny i trudniejszy do utrzymania.
- Nie używaj tego wzorca, jeśli warianty różnią się w wielu niezależnych miejscach.
- Unikaj go, gdy chcesz podmieniać cały algorytm w runtime.
- Ostrożnie podchodź do niego, gdy hierarchia klas zaczyna się rozrastać szybciej niż sam biznes.
- Nie komplikuj go, jeśli prosty helper albo funkcja z parametrami wystarczy.
Krótko mówiąc: jeśli wspólny jest tylko temat, ale nie przebieg, to nie jest dobry materiał na metodę szablonową. To prowadzi do porównania z innymi wzorcami, bo właśnie tam najłatwiej o pomyłkę.
Czym różni się od Strategy i hooków
To porównanie robi największą różnicę przy projektowaniu API i klas bazowych. Na pierwszy rzut oka oba wzorce pozwalają zmieniać zachowanie, ale robią to zupełnie inaczej. Tutaj nie chodzi o kosmetykę, tylko o mechanizm i moment wyboru wariantu.
| Cecha | Metoda szablonowa | Strategy |
|---|---|---|
| Co się zmienia | Pojedyncze kroki algorytmu | Cały algorytm lub jego większa część |
| Mechanizm | Dziedziczenie | Kompozycja i delegacja |
| Moment wyboru | Przy projektowaniu klasy | Przy konfiguracji albo w runtime |
| Najlepsze zastosowanie | Stały przebieg z lokalnymi wyjątkami | Wiele różnych sposobów wykonania tego samego zadania |
Hooki są jeszcze prostsze do zrozumienia: to opcjonalne punkty zaczepienia. Podklasa może je nadpisać, ale nie musi. Ja traktuję je jako bezpieczny wentyl dla drobnych rozszerzeń, a nie jako miejsce na ciężką logikę. Jeśli zaczynasz budować z hooków połowę biznesu, to zwykle znak, że architektura wymaga korekty.
Jeśli te różnice są jasne, projektowanie staje się dużo prostsze. Zostaje jeszcze ostatnia rzecz: jak wdrożyć ten wzorzec tak, żeby nie zamienił się w nadmiarową abstrakcję.
Jak wdrożyć go bez zbędnej abstrakcji
Najlepsze wdrożenia są zwykle krótsze, niż się wydaje na początku. Ja trzymam się kilku zasad, które chronią przed przeprojektowaniem kodu tylko dlatego, że wzorzec „ładnie wygląda” w diagramie:
- Trzymaj metodę szablonową możliwie krótką, najlepiej w granicach 4-7 kroków.
- Dodawaj tylko tyle hooków, ile naprawdę potrzebujesz, zwykle 1-3 w zupełności wystarczą.
- Oddziel kroki obowiązkowe od opcjonalnych, żeby czytelnik kodu nie musiał zgadywać, co jest ważne.
- Jeśli język na to pozwala, zabezpiecz główną metodę przed nadpisaniem.
- Testuj szkielet osobno od wariantów, bo wtedy szybciej wyłapiesz regresje w kolejności wywołań.
Jak sprawdzam, czy ten wzorzec nie usztywni projektu
Przed wdrożeniem zadaję sobie trzy pytania: czy przebieg procesu jest faktycznie stały, czy różnice dotyczą tylko lokalnych kroków i czy podklasy naprawdę opisują warianty jednego zadania. Jeśli na wszystkie trzy odpowiadam „tak”, metoda szablonowa ma sens. Jeśli nie, zwykle szybciej i czyściej wygrywa Strategy albo zwykła kompozycja.
To wzorzec bardzo użyteczny, ale tylko wtedy, gdy rzeczywiście porządkuje kod. Gdy zaczyna wymuszać sztuczną hierarchię, traci sens szybciej, niż pomaga. Właśnie dlatego lubię go traktować jako narzędzie do dyscyplinowania procesu, a nie jako obowiązkowy element każdej architektury obiektowej.