🔥 Zapisy zamknięte, ale możesz pobrać Roadmapę .NET i dołączyć do listy oczekujących — Pobierz i dołącz do Listy VIP →

Template Method w C# — anatomia GoF i szkielet algorytmu

Template Method — szkielet algorytmu w klasie bazowej z krokami nadpisywanymi przez podklasy w C#

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 szkielet

Kolejność (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.

👨‍💻
Mariusz Jurczenko
Senior .NET Developer · 10+ lat doświadczenia komercyjnego

Programista .NET z doświadczeniem komercyjnym w firmach takich jak NFZ, Kamsoft, Diagnostyka, Hermes Reply Polska czy Etisoft Smart Solutions. Twórca kursów, z których skorzystało już ponad 11 000 osób w Strefie Kursów i ponad 1 000 kursantów na dev-hobby.pl.

Specjalizacja: Clean Code, Clean Architecture i uczenie programowania tak, żeby dało się je naprawdę zrozumieć — nie wykuć.

🚀 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.

Dodaj komentarz

czytanie to początek

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ę →