Template Method w C# — anatomia GoF i szkielet algorytmu

Kolejny wzorzec z serii opartej na Anatomii wzorca — 8 elementach opisu GoF. Tym razem Template Method — wzorzec, który definiuje niezmienny szkielet algorytmu w klasie bazowej, oddając podklasom tylko te kroki, które faktycznie się różnią.
1. Intent (Zamierzenie)
Definiuje szkielet algorytmu w metodzie, przenosząc niektóre kroki do podklas. Pozwala podklasom przedefiniować wybrane kroki bez zmiany struktury całego algorytmu.
Krótko: klasa bazowa ustala kolejność kroków raz na zawsze, a podklasy dostarczają tylko treść wybranych kroków.
2. Also Known As (Inne nazwy)
Metoda szablonowa — polska nazwa oddaje istotę: szablon algorytmu z „dziurami” do wypełnienia przez podklasy.
3. Motivation (Motywacja)
Wyobraź sobie import danych do systemu: każdy format przechodzi te same kroki — wczytaj plik, sparsuj, zwaliduj, zapisz. Różni się tylko parsowanie (CSV inaczej niż JSON), reszta jest identyczna. Bez wzorca każdy importer kopiuje całą sekwencję, a poprawka w walidacji wymaga zmiany w kilku klasach naraz.
Template Method odwraca sterowanie: to klasa bazowa woła kroki podklasy w ustalonej kolejności („nie dzwoń do nas, my zadzwonimy do ciebie” — zasada Hollywood), a podklasa dostarcza tylko różniący się fragment.
4. Applicability (Kiedy stosować)
Stosuj Template Method gdy:
- wiele klas dzieli ten sam szkielet algorytmu, różniąc się pojedynczymi krokami,
- chcesz wyeliminować duplikację wspólnej struktury, zostawiając w podklasach tylko to, co naprawdę różne,
- budujesz framework, którego cykl życia (kroki) ma być stały, a użytkownik ma tylko wpiąć się w wybrane punkty.
5. Structure (Struktura)
┌──────────────────────────┐
│ AbstractClass │
│──────────────────────────│
│ +TemplateMethod() │ ← ustala kolejność, jest sealed
│ #Krok1() (abstract) │ ← dostarcza podklasa
│ #Krok2() (abstract) │
│ #Hook() (virtual, pusty)│ ← opcjonalny
└──────────────────────────┘
△
┌──────────┴──────────┐
┌──────────────┐ ┌──────────────┐
│ ConcreteA │ │ ConcreteB │
│ #Krok1() │ │ #Krok1() │
│ #Krok2() │ │ #Krok2() │
└──────────────┘ └──────────────┘TemplateMethod() wywołuje kroki w stałej kolejności. Kroki abstract muszą dostarczyć podklasy; Hook() to metoda virtual z pustą domyślną treścią — opcjonalny punkt zaczepienia.
6. Participants (Uczestnicy)
- AbstractClass — definiuje
TemplateMethod()(szkielet) oraz operacje pierwotne (abstract/virtual), z których się składa. - ConcreteClass — implementuje operacje pierwotne dla swojego wariantu algorytmu.
7. Collaborations (Współpraca)
ConcreteClass polega na AbstractClass w kwestii niezmiennych kroków algorytmu — dostarcza jedynie treść kroków zmiennych.
Kierunek wywołań jest odwrotny niż zwykle: to kod bazowy woła kod podklasy, nie na odwrót.
8. Consequences (Konsekwencje)
Plus: eliminuje duplikację — wspólna struktura żyje w jednym miejscu. Plus: odwrócenie sterowania pozwala kontrolować, które kroki podklasa może nadpisać. Minus: opiera się na dziedziczeniu, więc podklasa jest sztywno związana z bazą; przy wielu wariantach hierarchia rośnie, a kompozycja (Strategy) bywa elastyczniejsza.
—
Template Method w C# — od diagramu do kodu
### Scenariusz
Generator raportów: każdy raport pobiera dane, formatuje je i zapisuje. Kolejność jest stała — różni się tylko format wyjścia (PDF vs CSV).
### AbstractClass ze szkieletem
public abstract class GeneratorRaportu
{
// Template method — sealed, żeby podklasa nie zmieniła kolejności kroków
public string Generuj(IEnumerable<Zamowienie> dane)
{
var przefiltrowane = Filtruj(dane); // krok wspólny
var tresc = Formatuj(przefiltrowane); // krok zmienny (abstract)
PoWygenerowaniu(); // hook opcjonalny
return tresc;
}
private IEnumerable<Zamowienie> Filtruj(IEnumerable<Zamowienie> d) =>
d.Where(z => z.Kwota > 0); // niezmienny krok w bazie
protected abstract string Formatuj(IEnumerable<Zamowienie> dane); // dostarcza podklasa
protected virtual void PoWygenerowaniu() { } // hook — domyślnie nic
}### ConcreteClass — tylko różniący się krok
public class RaportCsv : GeneratorRaportu
{
protected override string Formatuj(IEnumerable<Zamowienie> dane) =>
string.Join("\n", dane.Select(z => $"{z.Id};{z.Kwota}"));
}
public class RaportJson : GeneratorRaportu
{
protected override string Formatuj(IEnumerable<Zamowienie> dane) =>
JsonSerializer.Serialize(dane);
protected override void PoWygenerowaniu() =>
Console.WriteLine("Raport JSON gotowy."); // podłączony hook
}GeneratorRaportu generator = new RaportCsv();
string csv = generator.Generuj(zamowienia); // filtr + format CSV, jeden szkieletKolejność (filtr → format → hook) jest w jednym miejscu. Nowy format (RaportXml) to jedna metoda Formatuj, bez dotykania logiki filtrowania.
Idiom C# — Template Method przez kompozycję
Template Method opiera się na dziedziczeniu. W C# ten sam efekt osiągniesz przez kompozycję — przekazując zmienny krok jako Func/delegat zamiast podklasy. To de facto Strategy:
public class Generator
{
public string Generuj(IEnumerable<Zamowienie> dane, Func<IEnumerable<Zamowienie>, string> formatuj)
{
var przefiltrowane = dane.Where(z => z.Kwota > 0);
return formatuj(przefiltrowane); // krok wstrzyknięty, nie nadpisany
}
}
// użycie: generator.Generuj(dane, d => string.Join("\n", d.Select(...)));Kompozycja jest elastyczniejsza (bez sztywnej hierarchii, krok wymienny w runtime). Dziedziczenie (klasyczny Template Method) wybierasz, gdy kroków jest wiele i tworzą spójną rodzinę wariantów.
Pułapka: metoda szablonowa musi być zamknięta
Jeśli Generuj() jest virtual, podklasa może ją nadpisać i zmienić kolejność kroków — a wtedy cały sens wzorca (stały szkielet) znika. Metoda szablonowa powinna być sealed albo niewirtualna; nadpisywać wolno tylko kroki (abstract/virtual), nie sam szkielet. To subtelny błąd, który łatwo przeoczyć.
Wariant: hooki jako opcjonalne kroki
Kroki abstract są obowiązkowe. Metody virtual z pustą domyślną treścią (jak PoWygenerowaniu) to hooki — opcjonalne punkty zaczepienia, które podklasa może nadpisać, ale nie musi. Rozróżnienie „obowiązkowy krok vs opcjonalny hook” projektujesz świadomie: abstract wymusza implementację, virtual ją tylko umożliwia. Fundamentem tego wyboru jest różnica między klasą abstrakcyjną a interfejsem.
Podsumowanie
Template Method zamyka kolejność kroków algorytmu w metodzie klasy bazowej, oddając podklasom wyłącznie treść kroków zmiennych — dzięki czemu wspólna struktura nie jest kopiowana. Sterowanie jest odwrócone: kod bazowy woła kod podklasy. Metoda szablonowa musi być zamknięta na nadpisanie, a rozróżnienie kroków obowiązkowych (abstract) od opcjonalnych hooków (virtual) jest częścią projektu. W C# realną alternatywą jest kompozycja (wstrzyknięty Func) — czyli Strategy — którą wybierasz, gdy wolisz elastyczność zamiast dziedziczenia.
🚀 Co dalej?
Zobacz to w praktyce na wideo i pobierz darmową roadmapę, żeby ułożyć naukę w spójną ścieżkę do pierwszej pracy.
- 🗺️ Pobierz darmową roadmapę Junior .NET Developer — 12 kroków od podstaw C# do pierwszej pracy: dev-hobby.pl
- 🎬 Subskrybuj kanał YouTube — nowe filmy co tydzień.
Zamień wiedzę w umiejętności
Pobierz darmową Roadmapę .NET i ułóż takie tematy jak ten w spójną ścieżkę do pierwszej pracy.
Pobieram roadmapę →