sobota, 1 marca 2014

Metoda szablonowa (klasowy)

Metoda szablonowa (Template method)


Metoda szablonowa jest wzorcem bardzo przyjemnym i łatwym w zrozumieniu.
Głównym metody szablonowej zadaniem jest zdefiniowanie metody, będącej szkieletem algorytmu. Algorytm ten może być następnie dokładnie definiowany w klasach pochodnych. Niezmienna część algorytmu zostaje opisana w metodzie szablonowej, której klient nie może nadpisać. W metodzie szablonowej wywoływane są inne metody, reprezentujące zmienne kroki algorytmu. Mogą one być abstrakcyjne lub definiować domyślne zachowania. Klient, który chce skorzystać z algorytmu, może wykorzystać domyślną implementację bądź może utworzyć klasę pochodną i nadpisać metody opisujące zmienne fragmenty algorytmu. Najczęściej metoda szablonowa ma widoczność publiczną, natomiast metody do przesłonięcia mają widoczność chronioną lub prywatną, tak, aby klient nie mógł ich użyć bezpośrednio.
Inna popularna nazwa tego wzorca to niewirtualny interfejs (ang. Non Virtual Interface).

Nazwa wzorca pomimo swojej nazwy nie ma nic wspólnego z szablonami jakich używamy w codziennym programowaniu w językach takich jak np. Cpp lub Java.

Przykład, najpierw bardzo teoretyczny :

Biblioteki, wspomagające automatyzację testów jednostkowych. Biblioteka jUnit wykorzystywana w programowaniu w języku Java definiuje ogólny algorytm wykonywania testów. Składa się on z trzech podstawowych kroków: przygotowania środowiska do wykonania testów, wykonania testów, a następnie posprzątania po wykonanych testach. Kroki te reprezentowane są przez metody setUp, runTest oraz tearDown. Wymienione metody wykonywane są w niezmiennej kolejności przez metodę run, której klient nie może zmienić. Dzięki temu, użytkownik nie może zmieniać kolejności podstawowych bloków algorytmu, może jednak nadpisać metody setUp, runTest oraz tearDown, co pozwala mu dostosować testy do swoich potrzeb. Najprostsza zmianką może być np. dodanie wyświetlenia tekstu jaka aktualnie metoda się wykonuje.

W internecie można znaleść również informacje o tym, że stosowanie tego wzorca często określane jest jako "zasada Hollywood", która mówi "nie dzwoń do nas, my zadzwonimy do Ciebie". Oznacza ona, że każdy z modułów aplikacji nie interesuje się tym skąd przychodzą żądania i dokąd kierowane są odpowiedzi. Wykonuje tylko postawione przed nim zadanie.

Metoda szablonowa jest podobnym wzorcem do wzorca strategii. Zanim jednak napisze na czym polega różnica chciałbym pokazać i wytłumaczyć na czym polega omawiany wzorzec. W rozdziale o strategi przedstawie różnice pomiędzy tymi dwoma wzorcami. Czas na przykład programu :

1. Ponizej pierwsza klasa abstrakcyjna, w której znajduje się metoda findSolution(), której definicja nie będzie już zmieniana w poniższych klasach. Jedynym zmianom implementacyjnym mogą ulec metody :
stepOne();
stepTwo();
stepThr();
stepFor();
Co więcej metody stepTwo() oraz stepThr() musza zostać zaimplementowane, ponieważ narazie są tylko abstrakcyjne.
abstract class Generalization {

   public void findSolution() {
      stepOne();
      stepTwo();
      stepThr();
      stepFor();
   }
   
   protected void stepOne() { System.out.println( "Generalization.stepOne" ); }
   
   abstract protected void stepTwo();
   abstract protected void stepThr();
   protected void stepFor() { System.out.println( "Generalization.stepFor" ); }
}
2. Następnie specjalizacja klasy Generalization. Jednak jak można zauważyć klasa ta pozostaje abtrakcyjna, ze względu na to, iż nie ma zaimplementowanej metody stepTwo(). Prosze zwrócić uwagę również na to, że metoda stepThr() wywołuje w sobie metody : 
step3_1(); 
step3_2(); 
step3_3(); 
Co warte również uwagi to to, że metoda step3_2() nie jest zdefiniowana w tej klasie.
abstract class Specialization extends Generalization {

   protected void stepThr() {
      step3_1();
      step3_2();
      step3_3();
   }
   
   protected void step3_1() { System.out.println( "Specialization.step3_1" ); }
   abstract protected void step3_2();
   protected void step3_3() { System.out.println( "Specialization.step3_3" ); }
}
3. Następna klasa to Realization, która dziedziczy po klasie Specialization, dlatego więc poniższa klasa musi implementować metodę step3_2(). Oprócz tego zmienionych zostało pare metod, które wykonywane są w metodzie findSolution() z klasy Generalization.
class Realization extends Specialization {

   protected void stepTwo() { System.out.println( "Realization   .stepTwo" ); }
   protected void step3_2() { System.out.println( "Realization   .step3_2" ); }

   protected void stepFor() {
      System.out.println( "Realization   .stepFor" );
      super.stepFor();
   }  
}
4. Na sam koniec testujemy naszą klasę wywołując metodę findSolution() na rzecz obiektu algorithm.
class TemplateMethodDemo {
   public static void main( String[] args ) {
      Generalization algorithm = new Realization();
      algorithm.findSolution();
   }  
}
Innym przykładem w teorii mogłoby być tworzenie pizzy. Metoda w klasie abstrakcyjnej nazywała by się twórz pizze i ta metoda nie ulegałaby żadnym zmianom. Byłby to algorytm do przygotowywania pizzy. 1. Stwórz ciasto 2. Dodaj składniki 3. Dodaj sos Każdą pizze tworzy się według powyższego algorytmu, a jedynie, co wyróżnia jedną pizzę od drugiej to jej ciasto, składniki, sos. Tak więc implementowalibyśmy tylko konkretne kroki algorytmu w klasach rzeczywistych. Innym przykładem może być gra. Poniżej umieszczam kod, a przeanalizowanie pozostawiam jako ćwiczenie:-).
public abstract class Gra
{
    private int liczbaGraczy;
    protected abstract void InicjalizujGrę();
    protected abstract void ZróbRuch(int gracz);
    protected abstract bool KoniecGry();
    protected abstract void WyświetlZwycięzcę();
    /// 
    /// Metoda szablonu
    /// 
    public void ZagrajRaz(int liczbaGraczy)
    {
        this.liczbaGraczy = liczbaGraczy;
        InicjalizujGrę();
        int j = 0;
        while (!KoniecGry())
        {
            ZróbRuch(j);
            j = (j + 1) % liczbaGraczy;
        }
        WyświetlZwycięzcę();
    }
}
public class Monopoly : Gra
{
    #region Gra
    protected override void InicjalizujGrę()
    {
        throw new Exception("The method or operation is not implemented.");
    }
    protected override void ZróbRuch(int gracz)
    {
        throw new Exception("The method or operation is not implemented.");
    }
    protected override bool KoniecGry()
    {
        throw new Exception("The method or operation is not implemented.");
    }
    protected override void WyświetlZwycięzcę()
    {
        throw new Exception("The method or operation is not implemented.");
    }
    #endregion
}
public class Szachy : Gra
{
    #region Gra
    protected override void InicjalizujGrę()
    {
        throw new Exception("The method or operation is not implemented.");
    }
    protected override void ZróbRuch(int gracz)
    {
        throw new Exception("The method or operation is not implemented.");
    }
    protected override bool KoniecGry()
    {
        throw new Exception("The method or operation is not implemented.");
    }
    protected override void WyświetlZwycięzcę()
    {
        throw new Exception("The method or operation is not implemented.");
    }
    #endregion
}
Na koniec diagram klas :

1)
2)


Drugi obraz dosyć dobrze odzwierciedla, to co się działo powyżej.

Brak komentarzy:

Prześlij komentarz