środa, 12 maja 2010

Template Method pattern

O wzorcu tym możemy przeczytać iż tworzy szablon dla implementacji algorytmu. Algorytm jest abstrakcyjny i dopiero klasy szczegółowe implementują część lub całość działania algorytmu (Wikipedia).

Jak wygląda ta definicja przenosząc ją do rzeczywistości?
A więc wyobraźmy sobie bardzo prostą czynność: robienie herbaty i robienie kawy.
Robienie herbaty:
  • Zagotuj wodę
  • Wsyp trochę ziaren herbaty do szklanki
  • Wlej gorącą wodę do szklanki
  • Dodaj cukru i cytryny
Robienie kawy:
  • Zagotuj wodę
  • Wsyp trochę kawy do szklanki
  • Wlej gorącą wodę do szklanki
  • Dodaj cukru i mleka
Jak widać algorytmy parzenia kawy i herbaty są bardzo zbliżone do siebie. Właściwie różnią się tylko w dwóch miejscach: składnikiem głównym (herbata, kawa) oraz dodatkami które dodajemy na końcu. Tak więc mamy dwie czynności takie same (gotowanie wody oraz wlanie gorącej wody do szklanki) i dwie różne. Napisanie dwóch klas, które realizują wszystkie te metody mija się z celem. Napiszemy kod, który będzie taki sam w dwóch metodach dla tych klas. Późniejsze zmodyfikowanie gotowania wody spowoduje, że zmiany będą musiały wystąpić w dwóch miejscach. Jak tego uniknąć?

Spójrzmy na UML:


Tak w skrócie wygląda wzorzec Template Method. Tworzymy jedną abstrakcyjną klasę która będzie nadzorowała cały przebieg algorytmu. W środku klasy definiujemy metody (poszczególne kroki algorytmu) które będą dwojakiego rodzaju: abstrakcyjne oraz zaimplementowane z góry. Te kroki które dla wszystkich kroków są takie same zostaną zaimplementowane w klasie abstrakcyjnej. Kroki które różnią się dla każdego z algorytmów zostaną zaimplementowane w konkretnych podklasach. Przejdźmy więc do rzeczy:

    public abstract class MakeDrink
    {
        public virtual void BoildWater()
        {
            Console.WriteLine("The water is boiling...");
        }

        public abstract void AddIngredien();

        public virtual void SpilCup()
        {
            Console.WriteLine("I spil the cup");
        }

        public abstract void AddCondiment();

        public void Run()
        {
            BoildWater();
            AddIngredien();
            SpilCup();
            AddCondiment();
        }
    }

    public class Tea : MakeDrink
    {
        public override void AddIngredien()
        {
            Console.WriteLine("I'm adding some tea leaf");
        }

        public override void AddCondiment()
        {
            Console.WriteLine("I'm adding sugar and limone");
        }
    }

    public class Coffee : MakeDrink
    {
        public override void AddIngredien()
        {
            Console.WriteLine("I'm adding some cofee beans");
        }

        public override void AddCondiment()
        {
            Console.WriteLine("I'm adding sugar milk");
        }
    }


    class Program
    {
        static void Main(string[] args)
        {
            MakeDrink md = new Tea();
            md.Run();
            Console.WriteLine();

            md = new Coffee();
            md.Run();
        }
    }

Jak widać, zostało zaimplementowane to wszystko, co wcześniej zostało napisane.
Z wzorcem Template Method związane są jeszcze tzw. hooked methods - czyli metody które można ale nie trzeba nadpisywać. Jeśli nie nadpiszemy ich w klasie potomnej - klasa abstrakcyjna zapewni im domyślne zachowanie. Dla przykładu pozwolimy wybrać użytkownikowi czy chce do kawy jakieś dodatki:

    public abstract class MakeDrink
    {
        public virtual void BoildWater()
        {
            Console.WriteLine("The water is boiling...");
        }

        public abstract void AddIngredien();

        public virtual void SpilCup()
        {
            Console.WriteLine("I spil the cup");
        }

        public abstract void AddCondiment();

        public void Run()
        {
            BoildWater();
            AddIngredien();
            SpilCup();
            if (CutomerWantCondiment())
            {
                AddCondiment();
            }
        }

        public virtual bool CutomerWantCondiment()
        {
            return true;
        }
    }

    public class Tea : MakeDrink
    {
        public override void AddIngredien()
        {
            Console.WriteLine("I'm adding some tea leaf");
        }

        public override void AddCondiment()
        {
            Console.WriteLine("I'm adding sugar and limone");
        }
    }

    public class Coffee : MakeDrink
    {
        public override void AddIngredien()
        {
            Console.WriteLine("I'm adding some cofee beans");
        }

        public override void AddCondiment()
        {
            Console.WriteLine("I'm adding sugar milk");
        }

        public override bool CutomerWantCondiment()
        {
            String answer = CustomerAnswer();
            if (answer == "y")
            {
                return true;
            }
            return false;
        }

        private string CustomerAnswer()
        {
            Console.WriteLine("Do you want some sugar and lemon for your tea? y/n");
            return Console.ReadLine();
        }
    }


    class Program
    {
        static void Main(string[] args)
        {
            MakeDrink md = new Tea();
            md.Run();
            Console.WriteLine();

            Coffee c = new Coffee();
            c.Run();
        }
    }

Tak więc dzięki Template Method uzyskaliśmy:
  • Łatwe zarządzanie algorytmem z jednego miejsca (klasy)
  • Zmniejszenie ilości powtarzanego kodu
  • Algorytm zapisaliśmy w jednym miejscu = łatwiejsza modyfikacja w przyszłości

Brak komentarzy:

Prześlij komentarz