sobota, 1 marca 2014

Strategia (obiektowy)

Strategia (Strategy)


Stosujemy wtedy gdy mamy możliwość wyboru kilku algorytmów wykonania różniących się właściwościami. Programista wybiera strategie, która będzie dla niego optymalna do wykonania. Innymi słowy strategia definiuje rodzinę wymiennych algorytmów i kapsułkuje je w postaci klas. Umożliwia wymienne stosowanie każdego z nich w trakcie działania aplikacji niezależnie od korzystających z nich klientów. 

Wzorzec ten jest podobny do wzorca metody szablonowej. Przedstawie różnice jakie występują pomiędzy tymi dwoma wzorcami po przeanalizowaniu pierwszego przykładu. 

Czas na przykład.

1. Na początek zostaje zdefiniowany interfejs strategii wraz z metodą solve(). Oczywiście nazwa metody może być dowolna.
interface Strategy { public void solve(); }
2. W strategii podobnie jak w metodzie szablonowej istnieje jedna metoda solve(), jednak różnica polega na tym, iż w przypadku strategii metodę tą możemy redefiniować. W strategii metody są różne i metoda solve() może być różnie zaimplementowana.
abstract class TemplateMethod1 implements Strategy {
   public void solve() {
      start();
      while (nextTry() && ! isSolution())
         ;
      stop();
   }
   protected abstract void    start();
   protected abstract boolean nextTry();
   protected abstract boolean isSolution();
   protected abstract void    stop();
}
3. Tworzymy teraz konkretną implementacje klasy TemplateMethod1. Z tego wynika, iż należy zdefiniować odpowiednio metody: 
start(); 
nextTry(); 
isSolution(); 
stop();
class Impl1 extends TemplateMethod1 {
   private int state = 1;
   protected void start() {
     System.out.print( "start  " );
   }
   protected void stop() {
     System.out.println( "stop" );
   }
   protected boolean nextTry() {
      System.out.print( "nextTry-" + state++ + "  " );
      return true;
   }
   protected boolean isSolution() {
      System.out.print( "isSolution-" + (state == 3) + "  " );
      return (state == 3);
   }
}
4. Nowa/inna klasa abstrakcyjna, która jest analogiczna do klasy TemplateMethod1. Prosze zwrócić uwagę jakie teraz klasa zawiera metody oraz jak została zaimplementowana metoda solve(). Na tym właśnie polega różnica, co do wzorca metody szablonowej.
abstract class TemplateMethod2 implements Strategy {
   public void solve() {                             
      while (true) {
         preProcess();
         if (search()) break;
         postProcess();
      }
   }
   protected abstract void preProcess();
   protected abstract boolean search();
   protected abstract void postProcess();
}
5. Analogiczna sytuacja do klasy Impl1. Tworzymy teraz konkretną implementacje klasy TemplateMethod2. Należy teraz odpowiednio zdefiniować metody: 
preProcess(); 
search(); 
postProcess();
class Impl2 extends TemplateMethod2 {
   private int state = 1;
   protected void    preProcess()  { System.out.print( "preProcess  " ); }
   protected void    postProcess() { System.out.print( "postProcess  " ); }
   protected boolean search() {
      System.out.print( "search-" + state++ + "  " );
      return state == 3 ? true : false;
   }
}
6. Na zakończenie krótki test naszych klas.
public class StrategyDemo {
   public static void clientCode( Strategy strat ) {
     strat.solve();
   }
   public static void main( String[] args ) {
      Strategy[] algorithms = { new Impl1(), new Impl2() };
      for (int i=0; i < algorithms.length; i++) {
         clientCode( algorithms[i] );
      }
   }
}
Zachecam do przeczytania wpisu o metodzie szablonowej. Dzięki temu będzie można łatwo rozróżnić różnice pomiędzy strategią, a metodą szablonową.

Diagram klas: 


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.

Dekorator (obiektowy)

Dekorator (Decorator)


Umożliwia dodanie nowej funkcjonalności dynamicznie podczas działania programu.

Wzorzec dekoratora polega na opakowaniu oryginalnej klasy w nową klasę "dekorującą". Zwykle przekazuje się oryginalny obiekt jako parametr konstruktora dekoratora, metody dekoratora wywołują metody oryginalnego obiektu i dodatkowo implementują nową funkcję.

Dekoratory są alternatywą dla dziedziczenia. Dziedziczenie rozszerza zachowanie klasy w trakcie kompilacji, w przeciwieństwie do dekoratorów, które rozszerzają klasy w czasie działania programu.

Jest to dosyć jasny wzorzec wiec szybko przejde do przykładu.

1. Na początek tworzymy interfejs Okno.
interface Okno {
    public void rysuj(); // rysuje Okno na ekranie
    public String pobierzOpis(); // zwraca opis Okna
}
2. Implementacja zwykłego okna bez pasków przewijania.
class ZwykłeOkno implements Okno {
    public void rysuj() {
        // rysuj okno
    }
 
    public String pobierzOpis() {
        return "zwykłe okno";
    }
}
3. Abstrakcyjna klasa dekorator - implementuje interfejs Okno. Klasa OknoDekorator przechowuje w sobie jako pole Okno. To właśnie okno będzie dekorowane, dlatego więc do konstruktora klasy przekazane zostanie okno, które będzie mialo zostać udekorowane. Jest to klasa abstrakcyjna wiec nie jest wymagane, aby implementowac wszystkie metody z interfejsu Okno.
abstract class OknoDekorator implements Okno {
    protected Okno dekorowaneOkno; // dekorowane Okno
 
    public OknoDekorator(Okno dekorowaneOkno) {
        this.dekorowaneOkno = dekorowaneOkno;
    }
}
4. Czas na pierwszy dekorator dodający pionowe paski przewijania. Każdy dekorator na swój sposób implementuje/dekoruje metode rysuj(). W klasie PionowePrzewijanieDekorator w metodzie rysuj() wywolujemy tez metode rysuj() na rzecz obiektu dekorowaneOkno.
class PionowePrzewijanieDekorator extends OknoDekorator {
    public PionowePrzewijanieDekorator (Okno dekorowaneOkno) {
        super(dekorowaneOkno);
    }
 
    public void rysuj() {
        rysujPionowyPasekPrzewijania();
        dekorowaneOkno.rysuj();
    }
 
    private void rysujPionowyPasekPrzewijania() {
        // rysuj pionowy pasek przewijania
    }
 
    public String pobierzOpis() {
        return dekorowaneOkno.pobierzOpis() + ", z pionowym paskiem przewijania";
    }
}
5. Drugi inny dekorator dodający poziome paski przewijania.
class PoziomePrzewijanieDekorator extends OknoDekorator {
    public PoziomePrzewijanieDekorator (Okno dekorowaneOkno) {
        super(dekorowaneOkno);
    }
 
    public void rysuj() {
        rysujPoziomyPasekPrzewijania();
        dekorowaneOkno.rysuj();
    }
 
    private void rysujPoziomyPasekPrzewijania() {
        // rysuj poziomy pasek przewijania
    }
 
    public String pobierzOpis() {
        return dekorowaneOkno.pobierzOpis() + ", z poziomym paskiem przewijania";
    }
}
6. Na sam koniec program testowy, który tworzy obiekt klasy Okno, dekoruje go poziomymi i pionowymi paskami przewijania i wypisuje jego opis. Prosze zwrócic uwage na sposób w jaki tworzymy dekorowane okno. Najpierw wywolujemy new dla pierwszego dekoratora, nastepnie do konstruktora tego dekoratora przekazujemy new drugiego dekoratora. Wszystko sie zgadza oba dekoratory przyjmuja jako argument do konstruktora obiekt klasy Okno oraz oba dekoratory dziedzicza po klasie OknoDekorator, a ta z koleji implementuje interfejs Okno. Na samo koniec inicjalizacji dekorowanego okna do konstruktora drugiego dekoratora przekazujemy zwykłeOkno. W następnej linijce pobieramy opis. Można dzięki takiemu zabiegowi łatwo sprawdzić czy wszystko się zgadza.
public class DekorowaneOknoTest {
    public static void main(String[] args) {
        // utwórz dekorowane Okno z poziomymi i pionowymi paskami przewijania
        Okno dekorowaneOkno = new PoziomePrzewijanieDekorator(
                new PionowePrzewijanieDekorator(new ZwykłeOkno()));
 
        // wypisz opis Okna
        System.out.println(dekorowaneOkno.pobierzOpis());
    }
}
Dekorator jest jednym z protszych wzorców. Myśle, że powyższy przykład dość dobrze odzwierciedlił zasade działania tego wzroca. Na koniec tradycyjnie diagram klas :


Metoda wytwórcza (klasowy)

Metoda wytwórcza (Factory method)

Umożliwia klasom przekazanie tworzenia egzemplarzy podklasom. Jak sama nazwa wskazuje wzorzec ten sluży do tworzenia obiektów. Jest podobny do wzorca budowniczy oraz fabryki abstrakcyjnej. Poniżej opisze różnice co wyróżnia metode wytwórczą od powyższych wzorców. Zanim jednak to zrobie wydaje mi się, że najłatwiej będzie zacząć od przykładu.
Podobnie jak dla budowniczego przykład będzie związany z pizza.


1. Na początek tworzymy klase abstrakcyjną Pizza wraz z metoda getPrice() zracającą cenę danej pizzy. Narazie jednak nie znamy ceny więc metoda pozostaje abstrakcyjna.
abstract class Pizza {
    public abstract double getPrice();
}
2. Konkretny rodzaj pizzy ze zdefiniowaną ceną.
class HamAndMushroomPizza extends Pizza {
    private double price = 8.5;
 
    public double getPrice() {
        return price;
    }
}
3. Analogicznie jak powyżej inny rodzaj pizzy z inna ceną.
class DeluxePizza extends Pizza {
    private double price = 10.5;
 
    public double getPrice() {
        return price;
    }
}
4. Nie wymaga komentarza.
class HawaiianPizza extends Pizza {
    private double price = 11.5;
 
    public double getPrice() {
        return price;
    }
}
5. Teraz natomiast tworzymy Fabryke pizzy, w której umieszczony został typ enum z konkretnymi rodzajami pizzy, takimi jak zdefiniowaliśmy powyżej, inaczej nie miałoby to sensu. W klasie tej jak widać poniżej mamy metode createPizza(). Jest to metoda statyczna, która jako argument przyjmuje konkretny rodzaj pizzy. Prosze zwrócić uwagę, iż rodzaj ten jest enumem zdefiniowanym właśnie w tej klasie. Na podstawie typu jaki metoda otrzymała zwraca konkretny rodzaj pizzy. Jeżeli argument będzie niepoprawny rzuci wyjątkiem. Można to zmienić. Każdy może na własny sposób zaimplementować taki przypadek zdarzenia:-). Ta klasa to "engine" całego wzorca. Tutaj widać różnice jaką wyróżnia się ten wzorzec.
class PizzaFactory {
    public enum PizzaType {
        HamMushroom,
        Deluxe,
        Hawaiian
    }

    public static Pizza createPizza(PizzaType pizzaType) {
        switch (pizzaType) {
            case HamMushroom:
                return new HamAndMushroomPizza();
            case Deluxe:
                return new DeluxePizza();
            case Hawaiian:
                return new HawaiianPizza();
        }
        throw new IllegalArgumentException("The pizza type " + pizzaType + " is not recognized.");
    }
}
6. Ostatni fragment kodu to testowanie/main programu. Tutaj można sprawdzić czy wszystko działa według naszych oczekiwań. Jest to jakaś możliwość przetestowania programu. Każdy może zrobić to na własny sposób. Nasz for, który jest de-facto foreach-em jako typ iterowany wybiera enuma z klasy PizzaFactory. Możemy o to poprosić, ponieważ jest to enum. Będziemy iterowali po całym zbiorze enuma, czyli stworzymy każdy rodzaj pizzy i podamy jego cene. Następnie korzystamy z metody statycznej z klasy PizzaFactory przekazując jej typ pizzy jaki chcemy stworzyć i od razu w jednej linijce prosimy o podanie ceny za dany rodzaj pizzy.
class PizzaLover {
    /**
     * Create all available pizzas and print their prices
     */
    public static void main (String args[]) {
        for (PizzaFactory.PizzaType pizzaType : PizzaFactory.PizzaType.values()) {
            System.out.println("Price of " + pizzaType + " is " + PizzaFactory.createPizza(pizzaType).getPrice());
        }
    }
}
Jak już wcześniej wspominałem wzorzec metoda wytwórcza jest podobny do przedwszystkim wzorca fabryka abstrakcyjna oraz budowniczego. Różnice, co do fabryki abstrakcyjnej są przedstawione powyżej w punkcie 5. W metodzie wytwórczej metoda createPizza jest metoda statyczna. Podobnie jak metoda GUIFactory z rozdziału o fabryce abstrakcyjnej. Jednak różnica polega na tym, iż teraz metoda createPizza() przyjmuje rodzaj konkretnej pizzy. Meoda GUIFactory sama na podstawie tego, co ustaliła wewnatrz samej siebie określała jakiego rodzaju przycisk ma stworzyć. Tak więc gdyby przełożyć przykład z fabryki abstrakcyjnej do metody wytwórczej musielibyśmy najpierw stworzyć odpowiedniego enuma z każdym rodzajem przycisku. Następnie przerobić metodę, tak by przyjmowała jako argument naszego enuma i na podstawie tego enuma zwracała odpowiedni typ przycisku. Podsumowywując zachęcam do przeczytania rodziału o fabryce abstrakcyjnej oraz budowniczym dla lepszego zrozumienia powyższych jak że podobnych do siebie wzorców. Różnice pomiędzy fabryką, a budowniczym opisałem w rozdziale o fabryce, dlatego warto choć raz przeczytać.

Na zakończenie rozważań o metodzie fabrykującej przedstawiam diagram klas:


Fabryka abstrakcyjna (obiektowy)

Fabryka Abstrakcyjna (Abstract Factory)


Skupia się na tworzeniu obiektów jednego typu (tej samej rodziny) bez specyfikowania ich konkretnych klas. Umożliwia jednemu obiektowi tworzenie różnych, powiązanych ze sobą podobiektów.

Czas na przykład, który mam nadzieje wyjaśni zasadę działania tego wzorca. Będzie to przykład ilustrujący fabrykę tworzenia przycisków pod różne systemy operacyjne.

1. Na początku tworzymy fabrykę, która posiada metodę statyczną getFactory() i zwraca odpowiedni typ już konkretnej fabryki na podstawie systemu, który zczytuje z pliku. Można implementować poniższy fragment na wiele różnych sposobów. Nie jest powiedziane, że za każdym razem budowa metody ma tak samo wyglądać. Chodzi mi tutaj np. o metodę readFromConfigFile(). W naszym przykładzie informacje o systemie na jakim pracujemy możemy równie dobrze zczytywać w jakiś inny sposób, albo mieć to w jakiś sposób zahardcodowane. Oprócz metody getFactory() mamy również metodę createButton(), którą każda już konkretna fabryka będzie implementowała na swój sposób.
/* Przykład GUIFactory */
abstract class GUIFactory {
    public static GUIFactory getFactory() {
        int sys = readFromConfigFile("OS_TYPE");
        if (sys == 0) {
            return new WinFactory();
        } else {
            return new OSXFactory();
        }
    }
    public abstract Button createButton();
}
2. Teraz czas na konkretna fabrykę - WinFactory, która rozszerza klasę abstrakcyjną GUIFactory i zarazem musi oraz implementuje metode createButton() zwracając tym samym już konkretny typ przycisku, a dokładniej pod system Windows.
class WinFactory extends GUIFactory {
    public Button createButton() {
        return new WinButton();
    }
}
3. Następnie analogicznie jak powyżej klasa - OSXFactory, która zwraca przycisk pod system OS.
class OSXFactory extends GUIFactory {
    public Button createButton() {
        return new OSXButton();
    }
}
4. Tworzymy klase abstrakcyjną Button. Jedyne, co w niej będziemy przechowywać to metoda abstrakcyjną paint(), która będzie implementowana już przez konkretne klasy przycisków pod Windowsa oraz OS-a.
abstract class Button {
    public abstract void paint();
}
5. Klasa WinButton rozszerza klasę Button i implemetuje metode paint(). Na swój sposób klasa ta będzie rysowała przycisk pod system Windows.
class WinButton extends Button {
    public void paint() {
        System.out.println("Przycisk WinButton");
    }
}
6. Analogicznie jak powyżej klasa OSXButton będzie rysowała przycisk w jakiś inny sposób. Oczywiście metody paint() w obu powyższych klasach nic nie robią oprócz wyświetlania jakiegoś komunikatu w konsoli, ale chodzi oczywiście tylko o zademonstrowanie mechanizmu działania wzorca:-).
class OSXButton extends Button {
    public void paint() {
        System.out.println("Przycisk OSXButton");
    }
}
7. Czas na ostatni fragment kodu - przetestowanie naszych klas. Najpierw tworzymy obiekt klasy GUIFactory. Metoda getFactory() była metodą statyczną więc bez problemów możemy ją wykonać nie mając obiektu klasy GUIFactory. Jak wcześniej napisałem metoda ta zwraca konkretną fabrykę, na podstawie rodzaju systemu jaki przeczyta z pliku konfiguracyjnego. W kolejnej linijce działamy wykorzystując polimorfizm, poniewaz nasz obiekt factory jest już konkretną implementacją i dlatego możemy na rzecz tego obiektu wywołać metodę createButton(), która zwróci nam konkretny typ przycisku. W następnej linijce możemy wyświetlić nasz przycisk i sprawdzić czy działa nasz program poprawnie. I tak jak jest napisane w komentarzu zostanie wyświetlony odpowiedni tekst "Przycisk WinButton" lub "Przycisk OSXButton". W zależności od tego jaki typ systemu będzie zdefiniowany w pliku konfiguracyjnym.
public class DekorowaneOknoTest {
    public static void main(String[] args) {
        // utwórz dekorowane Okno z poziomymi i pionowymi paskami przewijania
        Okno dekorowaneOkno = new PoziomePrzewijanieDekorator(
        new PionowePrzewijanieDekorator(new ZwykłeOkno()));
        // wypisz opis Okna
        System.out.println(dekorowaneOkno.pobierzOpis());
    }
}
Wzorzec Fabryka Abstrakcyjna jest podobnym wzorcem do wzorca Budowniczy oraz Metoda Wytwórcza i może się z tymi wzorcami mylić. Jaka jest więc różnica? Zachęcam do przeczytania postu o budowniczym jeżeli chce się przestudiować dokładne działanie tego wzorca. Jednak główna różnica polega na tym, iż w budowniczym mieliśmy w samej klasie PizzaBuilder (klasie abstrakcyjnej budowniczego) obiekt klasy Pizza (abstrakcyjnej klasy, której obiekty budowaliśmy). W fabryce nie mamy obiektu klasy, której produkujemy (naszego przycisku), a jedynie metode getFactory(), która zwraca konkretna fabrykę, a ta natomiast tworzy dynamicznie obiekt przyciku. Jakby to przełożyć na budowniczego to w budowniczym mielibyśmy obiekt abstrakcyjny przycisku i nastepnie byśmy go budowali, tzn. dodawali jakieś właściwości pod konkretny system. Tak jak było z pizza. Mieliśmy obiekt pizzy w abstrakcyjnej klasie budowniczego, a w konkretnych tylko budowalismy daną pizze. Budowaliśmy ciasto, sos, dodatki dla konkretnego rodzaju pizzy. W przypadku przyciku moglibyśmy "budować" np. tło porzycisku. W fabryce tego "przycisku" nie mamy, tej "pizzy" nie mamy, jako obiektu jakiejś klasy abstrakcyjnej tylko dopiero w momencie, gdy wiemy już jaki przycisk konkretnie stworzyć piszemy odpowiednio :
return new WinButton()
lub
return new OSXButton()
W przypadku "pizzy" byłoby np.
return new HawaiiPizza().
Mam nadzieje, że pomimo tego, że troche się rozpisałem każdy zrozumie jaka jest różnica pomiędzy tymi dwoma wzorcami. Zachęcam do przeczytania oddzielnych wpisów na temat budowniczego oraz metody fabrykującej.

Teraz czas na diagram klas tego wzorca.


niedziela, 23 lutego 2014

Fasada (obiektowy)

Fasada (Facade)

Użycie:

- Służy do ujednolicenia dostępu do złożonego systemu poprzez wystawienie uproszczonego interfejsu
- uporządkowanie interfejsu programistycznego, który ułatwia jego użycie
- pozwala na odseparowanie użytkownika, który tak naprawdę nie musi wiedzieć, co się dzieje wewnątrz systemu, jedynie zleca jakieś stosunkowo proste zadania
- upraszcza pisanie aplikacji

Przykłady:

a) Aplikacja bankomatowa:

Klient podchodząc do bankomatu ma bardzo ograniczone możliwości (prawa) w porównaniu z tym co umożliwia cały system. Funkcje klienta ograniczone są jedynie np. do wpłacenie pieniędzy, sprawdzenia stanu konta, wypłacenia pieniędzy. Wszystko to, co się dzieje po tym, jak klient wybierze daną operację to już go to nie obchodzi (a dzieję się tak naprawdę bardzo dużo). Cała wewnętrzna skomplikowana struktura jest zasłonięta dla użytkownika i dany jest mu jedynie jakiś prosty interfejs. W momencie, gdy użytkownik zleci np. dokonanie wpłaty system musi nawiązać połączenie z serwerem, musi nastąpić identyfikacja, pakiet musi być odpowiednio opakowany, w celach bezpieczeństwa oraz pewności dotarcia i wiele innych skomplikowanych rzeczy się dzieję.

b) Aplikacja sieciowa :

Klient ma tylko możliwość odebrania, bądz wysłania pakietu, a pod tym jest cala otoczka tzn. ustanowienie połączenia, synchronizacja, szyfrowanie, kompresja itp.

Diagram klas:





Bibliografia:

a) Erich Gamma, Richard Helm, Ralph Johnson, John Vlissides: Wzorce projektowe. Elementy oprogramowania obiektowego wielokrotnego użytku. Helion, 2010, s. 161-169. ISBN 978-83-246-2662-5.
b) http://pl.wikipedia.org/wiki/Fasada_(wzorzec_projektowy)

środa, 19 lutego 2014

Budowniczy (obiektowy)

Budowniczy (Builder)

Celem jest rozdzielenie sposobu tworzenia obiektów (punkt 2) od ich reprezentacji (punkt 1). Proces tworzenia obiektu podzielony jest na kilka mniejszych etapów (punkt 2), a każdy z tych etapów może być implementowany na wiele sposobów (punkt 3 i 4). Dzięki takiemu rozwiązaniu możliwe jest tworzenie różnych reprezentacji obiektów.

Sposób tworzenia obiektów zamknięty jest w oddzielnych obiektach (punkt 3, 4).

Zazwyczaj stosowany jest do konstrukcji obiektów złożonych, których konfiguracja i inicjalizacja jest procesem wieloetapowym (punkt 1, 2).

Przykład wraz z wyjaśnieniem powyższej definicji.

1. Najpierw tworzymy ogólny interfejs/produkt abstrakcyjny. To jest nasz szkielet. Teraz każdy nowy produkt będzie się tym różnił od pozostałych, tym że będzie w inny sposób implementował metody z klasy Pizza.
/** "Produkt" */
class Pizza {
  String dough = "";
  private String sauce = "";
  private String topping = "";
 
  public void setDough(String dough)     { this.dough = dough; }
  public void setSauce(String sauce)     { this.sauce = sauce; }
  public void setTopping(String topping) { this.topping = topping; }
}

2. Teraz tworzymy interfejs budowniczego. Każdy następny budowniczy będzie rozszerzał klase PizzaBuilder. Ważne jest w poniższym fragmencie to, że PizzaBuilder przechowuje obiekt Pizza w sekcji protected. Client z zewnątrz nie będzie miał bezpośredniego dostępu do tego obiektu. Każdy kolejny budowniczy, który będzie dziedziczył po klasie PizzaBuilder bedzie na swój sposób budował swoja pizze czyli implementował metody budujące pizze. Ponieważ w klasie PizzaBuilder mamy odpowiednio metody :
budujCiasto()
budujSos()
budujDodatki()
/** "Abstrakcyjny budowniczy" */
abstract class PizzaBuilder {
  protected Pizza pizza;
 
  public Pizza getPizza() { return pizza; }
  public void createNewPizzaProduct() { pizza = new Pizza(); }
 
  public abstract void buildDough();
  public abstract void buildSauce();
  public abstract void buildTopping();
}

3. Poniżej jak widać implementujemu konkretnego budowniczego, który ma już w sobie obiekt klasy Pizza (nasz budowniczy podziedziczył obiekt Pizza z klasy PizzaBuilder). Teraz już tylko budujemy nasza pizze na swój sposób. Implementujemy trzy podstawowe metody. Nic więcej nasz nie interesuje. Pierwszym rodzajem naszej pizzy będzie pizza Hawajska i w odpowiedni sposób dla tego rodzaju pizzy implementujemy nasze metody.
/** "Konkretny budowniczy" */
class HawaiianPizzaBuilder extends PizzaBuilder {
  public void buildDough()   { pizza.setDough("cross"); }
  public void buildSauce()   { pizza.setSauce("mild"); }
  public void buildTopping() { pizza.setTopping("ham+pineapple"); }
}

4. Inny rodzaj pizzy.
/** "Konkretny budowniczy" */
class SpicyPizzaBuilder extends PizzaBuilder {
  public void buildDough()   { pizza.setDough("pan baked"); }
  public void buildSauce()   { pizza.setSauce("hot"); }
  public void buildTopping() { pizza.setTopping("pepperoni+salami"); }
}

5. Teraz musimy stworzyć jakiegoś nadzorce, który będzie zajmował się zarządzaniem tworzenia pizzy. Dlatego więc tworzymy nową klasę Waiter, która będzie przechowywała obiekt klasy PizzaBuilder. Teraz, gdy będziemy chcieli stworzyć pizze będziemy najpierw tworzyć obiekt klasy Waiter, do którego konstruktora przekazać będzie trzeba budowniczego pizzy odpowiedzialnego za zbudowanie wybranego rodzaju pizzy. Wróćmy jeszcze do abstrakcyjnej klasy Pizza. Są tam metody getPizza() oraz createNewPizzaProduct(). Nie wspominałem o nich wcześniej, bo narazie nie były potrzebne. Teraz jednak już tak, ponieważ nasza kelnerka (nadzorca) będzie ustawiała, budowała i zwracała nasz pizze.
Zwróć uwagę jeszcze raz, że obiekt klasy Pizza jest jednym z pól klasy abstrakcyjnej PizzaBuiled. Czemu zwracam na to uwagę? Ponieważ jak wcześniej napisałem budowniczy jest podobny do wzorca Fabryka Abstrakcyjna czy Metoda Wytwórcza. A to jest  właśnie jedna z różnic. O szczegółach tych różnic w napisze w innych postach wzorca F.A. oraz M. W. Tak więc można powiedzieć, że kelnerka robi dużo. Niby tak, ale wywołuje tylko metody, które zostały zaimplementowane w innych klasach.
/** "Nadzorca" */
class Waiter {
  private PizzaBuilder pizzaBuilder;
 
  public void setPizzaBuilder(PizzaBuilder pb) { pizzaBuilder = pb; }
  public Pizza getPizza() { return pizzaBuilder.getPizza(); }
 
  public void constructPizza() {
    pizzaBuilder.createNewPizzaProduct();
    pizzaBuilder.buildDough();
    pizzaBuilder.buildSauce();
    pizzaBuilder.buildTopping();
  }
}

6. No i na sam koniec testujemy czy wszystko prawidłowo działa.
a) tworzymy obiekt klasy Waiter
b) następnie dwa rodzaje budowniczych chcąc utworzyć dwa rodzaje pizz
c) wywołujemy metodę setPizzaBuilder() przekazując rodzaj budowniczego
chcąc stworzyć wybraną pizze
d) wywołujemy metodę constructPizza() z klasy Waiter, żeby stworzyć naszą pizze
e) na koniec pobieramy pizze od kelnerki i przypisujemy do obiektu klasy Pizza.
Tak naprawdę linijka :
PizzaBuilder spicy_pizzabuilder = new SpicyPizzaBuilder();

jest niepotrzebna, ponieważ niekorzystamy z budowniczego do tworzenia pizzy ostrej, ale jeżeli ktoś chce może równie łatwo przekazać ten rodzaj budowniczego do Waiter i stworzyć ten rodzaj pizzy.
/** Klient zamawiający pizzę. */
class BuilderExample {
  public static void main(String[] args) {
    Waiter waiter = new Waiter();
    PizzaBuilder hawaiian_pizzabuilder = new HawaiianPizzaBuilder();
    PizzaBuilder spicy_pizzabuilder = new SpicyPizzaBuilder();
 
    waiter.setPizzaBuilder( hawaiian_pizzabuilder );
    waiter.constructPizza();
 
    Pizza pizza = waiter.getPizza();
  }
}

Budowniczy jest wzorcem podobnym do wzorca Fabryka Abstrakcyjna. Można go również mylić z wzorcem Metoda Wytwórcza. Czemu? Odpowiedź można znaleź w innych postach F. A i M. W. Wszystkie te wzorce, a mianowicie Budowniczy, Fabryka Abstrakcyjna oraz Metoda Wytwórcza (jak same ich nazwy wskazują) odpowiedzialne są za tworzenie obiektów, dlatego różnice pomiędzy nimi trudno jest na początku zauważyć.

Na koniec, gdy już poznaliśmy zasadę działania tego wzorca można spojrzeć na diagram klas.
Z diagramu widać, że wszystko się zgadza z powyższym rozumowaniem. Interfejs Budowniczy (budujCzęść()) -> KonkretnyBudowniczy. Nadzorca (Waiter) przechowuje budowniczego oraz posiada metodę konstruuj().