ś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().

Brak komentarzy:

Prześlij komentarz