Wzorce projektowe w programowaniu – wzorzec Budowniczy

Po co używać wzorca Budowniczy

Tematem niniejszego wpisu jest kolejny z wzorców projektowych w programowaniu – Budowniczy. Jest to wzorzec, który stosuje się w celu hermetyzowania tworzenia produktu i umożliwienia jego wieloetapowego inicjowania. Innymi słowy, ma rozwiązać problem tworzenia złożonych obiektów (takich, które wymagają wprowadzenia wielu argumentów podczas ich tworzenia), które będą tworzone wiele razy w różny sposób. Ogólnie rzecz biorąc, Budowniczowie to klasy, które ułatwiają tworzenie innych, złożonych w inicjalizacji obiektów. Zwracam uwagę, że implementację przedstawionego tutaj prostego kodu można by rozwiązać inaczej – w łatwiejszy sposób. Chodzi głównie o pokazanie idei omawianego wzorca.

Wzorzec Budowniczy w praktyce

Wyobraźmy sobie, że pracujemy w firmie zajmującej się budową domów. Aktualnie chcemy zbudować standardowy, ekonomiczny domek dla średnio zamożnej rodziny. Domek taki posiada określone właściwości (pola przedstawionej klasy).

public class DomStandard {

     public String okna {set; get;}
     public String ogrod {set; get;}
     public String basen {set; get;}
     public String brama {set; get;}
     public String garaz {set; get;}

     public DomStandard(String okna, String ogrod, String basen, String brama, String garaz) {
          this.okna = okna;
          this.ogrod = ogrod;
          this.basen = basen;
          this.brama = brama;
          this.garaz = garaz;
     }
}

Klasa posiada 5 pól oraz konstruktor. Aby stworzyć nowy dom stosujemy poniższy kod.

DomStandard domStandard = new DomStandard("Średnia wielkość", "Mały", "Brak", "Ręczna", "Mały");

Dla potrzeb niniejszego wpisu dodajmy klasę, która ma za zadanie „budować” tenże dom.

public class BudujDomStandard {

     public String okna {set; get;}
     public String ogrod {set; get;}
     public String basen {set; get;}
     public String brama {set; get;}
     public String garaz {set; get;}

     public void montujOkna() {
          this.okna = "Średnia wielkość";
     }

     public void tworzOgrod() {
          this.ogrod = "Mały";
     }

     public void budujBasen() {
          this.basen = "Brak";
     }

     public void montujBrame() {
          this.brama = "Ręczna";
     }

     public void budujGaraz() {
          this.garaz = "Mały";
     }
}

Budowa domu przebiega według pewnego schematu.

BudujDomStandard domStandard = new BudujDomStandard(0;
domStandard.montujOkna();
domStandard.tworzOgrod();
domStandard.budujBasen();
domStandard.montujBrame();
domStandard.budujGaraz();

Z czasem zdobyliśmy doświadczenie i chcieliśmy zająć się budową domów bardziej ekskluzywnych. Przeznaczonych dla bardziej wymagających klientów. Jednak taki dom ma inne komponenty – większe okna, wymyślny ogród a także wprowadzoną automatyzację niektórych jego elementów.

public class BudujDomPremium {

     public String okna {set; get;}
     public String ogrod {set; get;}
     public String basen {set; get;}
     public String brama {set; get;}
     public String garaz {set; get;}

     public void montujOkna() {
          this.okna = "Duże";
     }

     public void tworzOgrod() {
          this.ogrod = "Średni";
     }

     public void budujBasen() {
          this.basen = "Mały";
     }

     public void montujBrame() {
          this.brama = "Automatyczna";
     }

     public void budujGaraz() {
          this.garaz = "Duży";
     }
}

Co prawda nowy, ekskluzywny rodzaj nieruchomości ma inne komponenty, jednak sposób jego budowy pozostaje taki sam jak domu ekonomicznego.

BudujDomPremium domPremium = new BudujDomPremium(0;
domPremium.montujOkna();
domPremium.tworzOgrod();
domPremium.budujBasen();
domPremium.montujBrame();
domPremium.budujGaraz();

Rozdzielmy teraz stworzone klasy na te zajmujące się budową oraz przedstawiające sam dom.

public class DomStandard {
     public String okna {set; get;}
     public String ogrod {set; get;}
     public String basen {set; get;}
     public String brama {set; get;}
     public String garaz {set; get;}
}

public class DomPremium {
     public String okna {set; get;}
     public String ogrod {set; get;}
     public String basen {set; get;}
     public String brama {set; get;}
     public String garaz {set; get;}
}

public class BudowniczyDomStandard {

     public String okna {set; get;}
     public String ogrod {set; get;}
     public String basen {set; get;}
     public String brama {set; get;}
     public String garaz {set; get;}

     public void montujOkna() {
          this.okna = "Średnia wielkość";
     }

     public void tworzOgrod() {
          this.ogrod = "Mały";
     }

     public void budujBasen() {
          this.basen = "Brak";
     }

     public void montujBrame() {
          this.brama = "Ręczna";
     }

     public void budujGaraz() {
          this.garaz = "Mały";
     }
}

public class BudowniczyDomPremium {

     public String okna {set; get;}
     public String ogrod {set; get;}
     public String basen {set; get;}
     public String brama {set; get;}
     public String garaz {set; get;}

     public void montujOkna() {
          this.okna = "Duże";
     }

     public void tworzOgrod() {
          this.ogrod = "Średni";
     }

     public void budujBasen() {
          this.basen = "Mały";
     }

     public void montujBrame() {
          this.brama = "Automatyczna";
     }

     public void budujGaraz() {
          this.garaz = "Duży";
     }
}

Obie klasy: DomPremium oraz DomStandard mają te same właściwości. Stwórzmy więc dla nich jedną klasę.

public class Dom {
     public String okna {set; get;}
     public String ogrod {set; get;}
     public String basen {set; get;}
     public String brama {set; get;}
     public String garaz {set; get;}
}

Podobnie, oboje budowniczowie mają te same metody – a więc stwórzmy dla nich jeden, wspólny interfejs.

public interface IBudowniczyDomow {
     public void montujOkna();
     public void tworzOgrod();
     public void budujBasen();
     public void montujBrame();
     public void budujGaraz();
     Dom wydajZbudowanyDom();
}

public class BudowniczyDomStandard : IBudowniczyDomow {
...
...
}

public class BudowniczyDomPremium : IBudowniczyDomow {
...
...
}

Dodana została metoda wydająca zbudowany już obiekt (w momencie, kiedy któryś z budowniczych skończy budować obiekt (dom), nieruchomość można uznać za gotową). A kto zamawia wykonanie poszczególnych domów (obiektów) – oczywiście klient.

Odnośnie klienta – nie mamy go jeszcze zaimplementowanego. Klient będzie prosił o wykonanie danego obiektu przez budowniczego.

public class Klient {
     public void zbudujDom(IBudowniczyDomow budowniczy) {
          budowniczy.montujOkna();
          budowniczy.tworzOgrod();
          budowniczy.budujBasen();
          budowniczy.montujBrame();
          budowniczy.budujGaraz();
     }
}

Pokażmy teraz, jak to wszystko miałoby działać.

static void Main(string[] args)
{
	Klient klient = new Klient();
	IBudowniczyDomow standard = new BudownczyDomowStandard();
	IBudowniczyDomow premium = new BudowniczyDomowPremium();
 
	//buduj według logiki klienta - tak jak on chce, używając implementacji z standard
        klient.zbudujDom(standard);
        
        //buduj według logiki klienta - tak jak on chce, używając implementacji z premium
	klient.zbudujDom(premium);
 
	Dom domStandard = standard.wydajZbudowanyDom();
	Dom domPremium = premium.wydajZbudowanyDom();
}

Elementy wzorca Budowniczy oraz ich rola

Przeanalizujmy teraz podstawowe elementy, które występują w omawianym wzorcu:
– budowniczy (IBudowniczyDomow) – abstrakcyjny interfejs implementowany przez poszczególnych rzeczywistych budowniczych, który służy do budowania finalnego produktu,
– konkretny, rzeczywisty budowniczy (BudowniczyDomowStandard oraz BudowniczyDomowPremium) – dostarcza implementację metodom budowniczego,
– klient, kierownik (Klient) – konstruuje obiekt z wykorzystaniem konkretnego budowniczego – klient dostarcza logikę, według której ma zostać utworzony przez budowniczego obiekt,
– produkt (domStandard, domPremium) – złożony obiekt.

Dwa najważniejsze elementy tego wzorca do konkretny, rzeczywisty budowniczy oraz klient, kierownik. Pierwszy z nich implementuję logikę budowania, którą z kolei dostarcza klient. Dzięki oddzieleniu logiki od implementacji możliwa jest zmiana sposobu budowania obiektu (logiki) w jednym miejscu i automatyczne zaimplementowanie tych zmian w całym systemie (każdy budowniczy przejmie tenże sposób).
Interfejs budowniczy ma zapewnić wspólny interfejs polimorficzny dla rzeczywistych budowniczych.

Budowniczy a Fabryka

Oba wzorce, zarówno omawiany tutaj Budowniczy jak i Fabryka, są wzorcami kreacyjnymi. Jeżeli przyjrzymy się powyższemu przykładowi, możemy dojść do wniosku, że są one do siebie bardzo podobne. Jednak podstawową różnicą jest to, że Budowniczy służy do kreacji skomplikowanych obiektów – stosowany powinien być wtedy, gdy instancji obiektu nie da się zahermetyzować w jednej funkcji i budowanie obiektu jest zbyt złożone nawet dla Fabryki Abstrakcyjnej. Dodatkową różnicą jest to, że fabryka tworzy instancję od razu a budowniczy – przekazuje nam ją na żądanie.

Inny rodzaj wzorca Budowniczy

W programowaniu korzysta się także z pewnej prostszej odmiany omawianego wzorca, nazywanej Statycznym Budowniczym.

Opiera się on na użyciu tzw. metody fluent interface. Charakteryzuje się ona tym, że każda metoda w danej klasie zwraca instancję klasy, w której się znajduje. Daje to nam wiele korzyści:
– jesteśmy w stanie budować skomplikowane obiekty be oddzielania logiki od implementacji,
– upraszczamy złożone konstruktory,
– możliwe jest tworzenie obiektów niemutowalnych.

Implementacja Statycznego Budowniczego może wyglądać następująco.

public class BudowniczyDomow {

     Dom dom = new Dom();

     public BudowniczyDomow montujOkna(String rodzaj) {
          dom.okna.dodaj(rodzaj);
          return this;
     }

     public BudowniczyDomow budujBasen(String rodzaj) {
          dom.basen.dodaj(rodzaj);
          return this;
     }

     public BudowniczyDomow tworzOgrod(String rodzaj) {
          dom.ogrod.dodaj(rodzaj);
          return this;
     }
}

static void Main(String[] args) {

     Dom dom = new BudowniczyDomow();
          .montujOkna("Średnie");
          .budujBasen("Mały");
          .tworzOgrod("Duży");
}

Statyczny Budowniczy nie jest oficjalnym wzorcem projektowym, niekiedy jest także nazywany po prostu Budowniczy i tym samym mylony z oryginalnym wzorcem.