Bob Budowniczy - Zawsze da radę!
Wbrew pozorom branża IT ma zadziwiająco dużo wspólnego z budownictwem. Pierwsze metodologie tworzenia architektury oprogramowania wyglądały łudząco podobnie do planów stawiania budynków – idziemy od fundamentów w górę aż uda się położyć dach. Dość powiedzieć, że to właśnie z budowlanki pochodzi nazwa „konstruktor”.
Kiedy zacząłem swoją przygodę z programowaniem dużo się głowiłem nad (z dzisiejszego punktu widzenia) banałami. Np. takimi jak:
Czym właściwie jest konstruktor?
Jeśli teraz miałbym odpowiedzieć na to pytanie najprościej jak to możliwe to użyłbym właśnie porównania do placu budowy. Mamy tam cegły, piasek i cement – tyle ile chcemy. To litery z których możemy napisać taki kod jaki jest nam potrzebny. Z nich zrobimy fundamenty, ściany, schody i strop – podstawowe elementy każdej chałupy.
Ale przyznacie, że w takim czymś trudno jeszcze zamieszkać. Przydałyby się drzwi i okna, kanalizacja, elektryka, dach… Tego sami nie możemy zrobić bo to inna branża. Musimy posiłkować się obiektami innych klas. Niektóre z wymienionych rzeczy są niezbędne – plan konstrukcji (czyli konstruktor) musi to uwzględnić – jeśli np. nie będziemy mieli dachu to nawet nie ma co zaczynać budowy.
Obiekt „Dach” po prostu MUSI się znaleźć w parametrach konstruktora!
Inne części domu mogą być opcjonalne, dajmy na to kominek. Bez nich plan konstrukcji się obejdzie, co najwyżej dostawimy je potem używając settera.
public void setFireplace(Fireplace fireplace){
this.fireplace = fireplace
}

Czasami zdarza się, że tych obowiązkowych parametrów jest za dużo. Wtedy możemy wspomóc się przeładowaniem konstruktora wraz z użyciem wartości domyślnych:
public House (Roof roof){
this.roof = roof;
heating = new RegularHeating();
electricity = new Standard Electricity();
plumbing = new BasicPlumbing();
}
Mając taki konstruktor jak powyżej wszystkie niezbędne elementy są ustawione, ale pojawia się też pokusa użycia go do zbudowania szopki narzędziowej, której wystarczy dach i elektryka, a resztę wartości można ustawić na „null”. Więc warto stworzyć osobny konstruktor (mówi się: „przeładować” (ang. overload)). A potem jeszcze jeden dla domku letniskowego i dla budy dla psa i dla…

Widzicie już do czego to zmierza? Konstruktory mnożą się jak króliki, a kod staje się nieczytelny. Aby zaradzić temu problemowi wymyślono wzorzec „Budowniczy” (Builder):
Builder – hermetyzuje operacje niezbędne do stworzenia złożonego obiektu ukrywając ich wewnętrzną strukturę przed klientem. Umożliwia tworzenie obiektów w procedurze wielokrokowej i nie narzuca tej procedury.
Rusz głową! Wzorce projektowe
Ale po kolei…
Jednym z najczęściej spotykanych przeciwników w światach fantasy są Orkowie. Były we Władcy pierścieni, w Warcrafcie, Warhammerze, DnD, Gothicu, grach z serii DivineDivinity czy Heroes. Ogólny opis orka jest powszechnie znany: silny ale niezbyt bystry, agresywny i zielony. Kiedy jednak padnie pytanie:
Czy Orkowie to trudni przeciwnicy?
To pojawia się dylemat. Z jednej strony mamy zwykłych piechurów, którzy padają od strzału. Z drugiej orkowi watażkowie często pełnią rolę minibossów. Są Orkowie łucznicy, beznadziejni w zwarciu ale zabójczy na dystans. Są szamani wspomagający swoje jednostki. Są jeźdźcy dzików, tarczownicy, kamikadze, władcy zwierząt. Nawet w świecie programowania są (framew)Orki 😉

Ogólnie rzecz biorąc – dużo ich jest. I teraz pomyślmy jak ich wszystkich zakodować w naszej grze. Oczywiście możemy zrobić abstrakcyjnego orka i po nim dziedziczyć, ale ilość powstałych typów nas szybko przytłoczy. Już nie mówiąc nawet o masowej duplikacji kodu. Nie tędy droga.
Z pomocą przychodzi Builder
Jako, że Builder w założeniu jest klasą „nested” czyli zagnieżdżoną (statyczną klasą w innej klasie) stworzyłem dwa pliki z kodem. Pierwszy (tutaj) pokazuje jak Budowniczy działa, a drugi (tutaj) jak wygląda jego implementacja.

Jak widzicie, stworzyłem trzech orków: orc, orcArcher, orcChieftain. Pierwszy jest implementacją domyślną – jego parametry zostawiłem takie jak ustawione w klasie i wyszedł mi podstawowy model.

Nazwa klasy „budującej” orki to OrcBuilder który tworzy swój obiekt i „wstrzykuje” go do konstruktora klasy Orc. Jest ustawiony jako prywatny, aby tylko Builder miał do niego dostęp:
private Orc(OrcBuilder orcBuilder) {
this.strength = orcBuilder.strength;
this.hp = orcBuilder.hp;
this.attackSpeed = orcBuilder.attackSpeed;
this.rank = orcBuilder.rank;
this.attack = orcBuilder.attack;
}
Konstruktor ma aż 5 potrzebnych parametrów, dlatego lepiej unikać jego przeładowania i użyć Buildera. Ten albo stworzy obiekt domyślny, albo pozwoli klientowi na samodzielne ustawienie pożądanych elementów – od tego są settery:

Każdy setter zwraca swój obiekt OrcBuilder (return this;) dlatego mogą się na siebie nakładać. Kiedy budowa jest już zaplanowana, kończymy ją pleceniem build() zwracającym nam gotowego Orka.
public Orc build(){
return new Orc(this);
}
Oczywiście nic nie stoi na przeszkodzie, aby zrobić schemat Buildera pod konkretne typy, tak aby nie musieć za każdym razem robić tego ręcznie. Podklasa OrkChieftain zawiera tylko metodę build() uruchamiającą wszystkie wybrane settery:

Podsumowując:
- Wzorca Builder używamy wtedy kiedy konstruktor danej klasy przyjmuje za dużo parametrów (> 3).
- Aby stworzyć prostego budowniczego piszemy klasę zagnieżdzoną (static class) wewnątrz docelowej klasy.
- Konstruktor docelowej klasy robimy prywatnym i użależniamy go tylko od obiektu Buildera.
- Klasa Builder ma mieć zainicjalizowane te same parametry co klasa docelowa, warto im nadać wartości domyślne.
- Każdy z tych parametrów musi mieć specjalny setter zwracający obiekt Buildera.
- Ostatnia metoda Buildera to build() która zwraca obiekt klasy docelowej poprzez użycie jej konstruktora z wstrzykniętym własnym obiektem.
ITCandidateEvaluator
Spośród dodatkowych wzorców, pokrótce omówionych na samym końcu podręcznika Rusz Głową! nieprzypadkowo wybrałem właśnie Buildera. Myślę, że świetnie nada się do tworzenia obiektów Rekrutacja na samym początku aplikacji. Chciałbym dać użytkownikowi wybór jakie elementy rekrutacji pojawią się w jej trakcie – np. będzie mógł zaznaczyć czy chce odpytywać kandydata ze znajomości języka angielskiego, albo czy przewiduje tzw. „live coding„. Tych parametrów będzie więcej i uważam, że Builder najlepiej sprawdzi się w ich zarządzaniu.
Do końca tego tygodnia planuję jeszcze opisanie dwóch wzorców. Następny będzie Chain of Command (Łańcuch zarządzania), a po nim wezmę się za wzorzec złożony Model-View-Controller. Kolejny krokiem będzie już praca nad moim drugim projektem.
Do omówienia zostaną jeszcze takie wzorce jak:
- Bridge
- Flyweight
- Interpreter
- Mediator
- Memento
- Prototype
- Visitor
Szybka lektura opisów ich zastosowań przekonała mnie, że nie są aż tak kluczowe jak te, którymi zajmowaliśmy się do tej pory. Być może kiedyś jeszcze wrócę do tematu, ale teraz są ważniejsze rzeczy do wykonania.
Jak zwykle – dziękuję za uwagę i zapraszam do następnych artykułów.