Przejdź do głównej zawartości

[OOP] Na czym polega wzorzec projektowy metoda szablonowa?


Metoda szablonowa (ang. template method) jest wzorcem projektowym, który pozwala na zdefiniowanie szkieletu zachowania lub algorytmu w klasie abstrakcyjnej i jego uszczegółowienie w klasach pochodnych. Template method jest rodzajem czynnościowego wzorca projektowego. Więcej o typach wzorców projektowych można poczytać tutaj [under construction].

Przykładem mogłaby być klasa generująca raporty. Raporty mogą być różne - finansowe, zawierające dane o ilości sprzedanych produktów albo o ilości jajek zniesionych przez kury na farmie. Jednak posiadają one pewną cechę wspólną - dla każdego raportu chcemy mieć nagłówek mówiący czego on dotyczy, jego ciało zawierające główną treść, i stopkę z informacją o dacie wygenerowania.

Nie chcemy żeby każda z klas FinancialReportPrinter, SalesReportPrinter i HensWorkReportPrinter zawierała w sobie logikę mówiącą, że na górze raportu jest header, potem body, a potem footer. Byłoby to zbędne powtarzanie. W razie potrzeby dodania podsumowania między ciałem, a stopką z datą, trzeba by wprowadzić zmiany w każdej z tych klas, co byłoby nie tylko denerwujące, lecz także narażałoby kod na błędy.

Dlatego lepiej jest zastosować wzorzec template method i zdefiniować abstrakcyjną klasę ReportPrinter, w której zawrze się ogólna logika generowania raportu. Dziedziczyć z niej będą klasy FinancialReportPrinter, SalesReportPrinter i HensWorkReportPrinter i każda z nich doprecyzuje na czym właściwie będzie polegać drukowanie poszczególnych sekcji.

W razie potrzeby dodania do wszystkich raportów nowej sekcji, logikę zmienimy tylko w klasie bazowej. W klasach pochodnych pozostanie nam tylko dopisanie implementacji nowej metody drukującej dodaną sekcję.


    public abstract class ReportPrinter
    {
        public void Print()
        {
            PrintHeader();
            PrintBody();
            PrintFooter();
        }

        protected abstract void PrintHeader();
        protected abstract void PrintBody();
        protected abstract void PrintFooter();
    }

    public class HensWorkReportPrinter : ReportPrinter
    {
        protected override void PrintHeader()
        {
            Console.WriteLine("Raport o zniesionych jajkach:");
        }

        protected override void PrintBody()
        {
            Console.WriteLine("Zniesiono 16 jajek.");
        }

        protected override void PrintFooter()
        {
            Console.WriteLine("Raport o jajkach wygenerowano w dniu " + DateTime.Now.ToShortDateString());
        }
    }

    public class SalesWorkReportPrinter : ReportPrinter
    {
        protected override void PrintHeader()
        {
            Console.WriteLine("Raport o sprzedaży:");
        }

        protected override void PrintBody()
        {
            Console.WriteLine("Sprzedano czterech ziomków na psach.");
        }

        protected override void PrintFooter()
        {
            Console.WriteLine("Raport o sprzedaży wygenerowano w dniu " + DateTime.Now.ToShortDateString());
        }
    }

Innym przykładem zastosowanie tego wzorca mogą być frameworki do pisania unit testów. W większości z nich kroki wykonywania testów są podobne - najpierw wołamy metodę która przygotowuje środowisko testowania, potem wywołuje się ciało testu, a na koniec dokonujemy weryfikacji czy test przeszedł, czy nie.

Podobnie sprawa ma się z grami planszowymi, w których rozgrywka zawsze zaczyna się od rozłożenia planszy, potem od właściwej gry, a kończy się podliczeniem punktów i wyłonieniem zwycięzcy. Przykład ten omawiam w tym poście.

Kod z tego posta, wraz z innymi przykładami wzorców projektowych, można pobrać z tego repozytorium [under construction]

Komentarze