Blog
Budowniczy
Powrót do Artykułów Dodał(a) Bartłomiej Zagórski w dniu 05.05.2021 w kategorii Wzorce projektowe
Budowniczy jest to konstrukcyjny wzorzec projektowy, który ma na celu oddzielenie procesu tworzenia obiektu od jego reprezentacji. Jest wykorzystywany przy tworzeniu skomplikowanych obiektów (kompozytów), które wymagają wielokrokowego procesu inicjalizacji. Ponadto pozwala zastosować kilka reguł porządnego programowania obiektowego. W poniższym artykule postaram się pokrótce pokazać kiedy można go zastosować.
Kiedy stosować wzorzec budowniczy?
Budowniczy to wzorzec, który pozwala na hermetyzację procesu tworzenia obiektu, a także na wieloetapową inicjalizację tegoż obiektu. Co to wszystko oznacza? Otóż wyobraźmy sobie sytuację, że tworzymy obiekt komputer. Komputery są różne: stacjonarne, laptopy, specjalistyczne (np. do diagnostyki samochodowej). Proces tworzenia każdego z nich posiada wiele różnych etapów. Na obiekt Komputer składają się mniejsze obiekty, które sprawiają, że proces inicjalizacji nie może przebiegać w jednym etapie. Wówczas z pomocą przychodzi wzorzec budowniczy. Pozwala on na tworzenie kompozytowych obiektów w bardzo elastyczny sposób. Spójrzmy na przykład:
public class BudowniczyTest {
public static void main (String[] args) {
Komputer komputer = new Komputer("karta GeForce", "Procesor Intel", "SSD");
Komputer komputer2 = new Komputer();
komputer2.setDysk("SSD");
komputer2.setKartaGraficzna("karta GeForce");
komputer2.setProcessor("Intel");
}
}
W powyższym kodzie klasa BudowniczyTest będąca naszym klientem tworzy obiekt klasy Komputer bezpośrednio. Klasa ta musi mieć pełną świadomość jak zbudowana jest struktura komputera, gdyż inicjalizacja musi nastąpić albo poprzez specjalnie przygotowany konstruktor lub poprzez wywołanie w pewnej kolejności kilku metod. W powyższym przykładzie obiekt Komputer jest oczywiście bardzo prosty, ale mogłaby to być np. struktura drzewiasta, z wieloma węzłami itp. Ponadto różne rodzaje komputerów mogą być budowane poprzez wywołanie różnej ilości różnych metod. W związku z tym chcąc dodać nowy rodzaj komputera, musielibyśmy edytować zarówno klasę klienta jak i Komputer, co godzi w reguły dobrego projektowania. Co należy zatem zrobić? Wprowadzić wzorzec budowniczy!!! Spójrzmy na implementację.
public interface BudowniczyKomputera {
void zbudujKarteGraficzna();
void zbudujDysk();
void zbudujProcesor();
Komputer getKomputer();
}
Powyżej widzimy interfejs BudowniczyKomputera określający jakie metody należy zaimplementować w rzeczywistych implementacjach. Dzięki wprowadzeniu interfejsu wykorzystamy w programie mechanizm polimorfizmu, który pozwala na odwrócenie zależności, gdyż to klasy podrzędne będą decydowały o rzeczywistej reprezentacji budowniczego. Następnie przejdźmy do przykładowej implementacji.
public class RzeczywistyBudowniczyKomputera implements BudowniczyKomputera {
private Komputer komputer = new Komputer();
@Override
public void zbudujKarteGraficzna() {
komputer.setKartaGraficzna("GeForce");
}
@Override
public void zbudujDysk() {
komputer.setDysk("SSD");
}
@Override
public void zbudujProcesor() {
komputer.setProcessor("Intel");
}
@Override
public Komputer getKomputer() {
return this.komputer;
}
}
Rzeczywisty budowniczy implementuje interfejs Budowniczy. Dzięki takiemu rozwiązaniu możemy utworzyć wielu budowniczych i w dynamiczny sposób podmieniać ich w programie. To sprawia, że postępujemy zgodnie z zasadą „bądź otwarty na rozbudowę, a zamknięty na modyfikacje”. Rzeczywisty budowniczy jest bardzo istotny, gdyż w metodzie getKomputer() dostarcza rzeczywistą reprezentację Komputera. Jednak równie ważna jest następna klasa a mianowicie Dyrektor.
public class Dyrektor {
public void tworzKomputer(BudowniczyKomputera budowniczy) {
budowniczy.zbudujDysk();
budowniczy.zbudujKarteGraficzna();
budowniczy.zbudujProcesor();
}
}
Klasa dyrektor jest odpowiedzialna za logikę tworzenia poszczególnych komputerów. Wykorzystuje ona luźne powiązanie z budowniczym. Jak już wspomniałem, dzięki polimorfizmowi możemy w bardzo elastyczny sposób podmieniać budowniczego, a więc implementacje metod mogą być różne. Ponadto możemy skorzystać również z innego Dyrektora, a zatem cała logika tworzenia może zostać zmodyfikowana bez konieczności zmiany w istniejących klasach !!! Zobaczmy na kod programu.
public class BudowniczyWzorzec {
public static void main(String[] args) {
Dyrektor dyrektor = new Dyrektor();
BudowniczyKomputera budowniczy = new RzeczywistyBudowniczyKomputera();
dyrektor.tworzKomputer(budowniczy);
Komputer komputer = budowniczy.getKomputer();
}
}
Klient tworzy obiekty Dyrektora i Budowniczego. Następnie dyrektor zleca budowniczemu przygotowanie egzemplarza komputera. Na koniec klient pobiera komputer od budowniczego. Udało nam się zatem uzyskać wiele wymiernych korzyści. Po pierwsze klient nie wie nic zarówno o strukturze przechowywania komputera jak i procesie jego powstawania. Po drugie możemy stworzyć wewnątrz budowniczego obiekt Komputer przy pomocy Dyrektora, ale nie musimy go natychmiast otrzymać, klient zgłasza się po Komputer bezpośrednio do budowniczego. Po trzecie i chyba najważniejsze możemy dynamicznie zmieniać zarówno logikę tworzenia komputera w obiekcie Dyrektora, jak również poszczególne implementacje metod, dzięki implementacji wspólnego interfejsu przez różnych budowniczych.