Wzorce projektowe – Budowniczy
Budowniczy jest kreacyjnym wzorcem projektowym, a więc takim, który tworzy nowe obiekty, często bardzo rozbudowane i implementujące inne obiekty. Celem użycia tego wzorca jest uporządkowanie sposobu tworzenia obiektów, co daje możliwość łatwego sterowania tym procesem.
Charakterystyka wzorca budowniczy
Zadaniem wzorca budowniczego (builder) jest rozdzielenie sposobu tworzenia obiektów od ich reprezentacji. Budowanie obiektu oparte jest na jednym procesie konstrukcyjnym i podzielone jest na mniejsze etapy. Zaletą wykorzystania tego wzorca jest możliwość łatwego sterowania, w jaki sposób przebiega proces tworzenia obiektów oraz większa skalowalność kodu. Ponadto izolujemy ten proces, który często może być bardzo skomplikowany.
Na pierwszy rzut oka budowniczy swoją strukturą przypomina wzorzec strategii. Zasadnicza różnica polega jednak na jego przeznaczeniu. Strategia jest wzorcem czynnościowym, którego celem jest wykonywanie konkretnych zadań. Budowniczy natomiast, jak sama nazwa wskazuje, skupia się na tworzeniu (budowaniu) i zwracaniu konkretnych obiektów, które często są bardzo skomplikowane.
Poniższy diagram klas UML pokazuje podstawową strukturę kodu w opisywanym wzorcu.
Omówienie diagramu zacznijmy od końca. Produkt to obiekt, które chcemy tworzyć (budować) w naszym programie. Budowniczy dostarcza interfejs do tworzenia tego obiektu, natomiast Konkretny Budowniczy, który implementuje ten interfejs, jest konkretną reprezentacją Produktu. Na końcu mamy Nadzorcę (inaczej Kierownik, Dyrektor), który zleca budowę Produktu za pomocą Budowniczego oraz dba o proces konstrukcyjny.
Przykład zastosowania wzorca budowniczy – budujemy robota
Poniżej opisany został przykład użycia wzorca projektowego budowniczy, na podstawie skryptu w języku PHP, którego zadaniem jest zbudowanie prostego obiektu robota.
W pierwszej kolejności skupmy się na klasie Produktu, czyli w tym przypadku klasie naszego Robota. Robot implementuje interfejs, przy pomocy którego ustawiane są poszczególne części takie jak głowa, korpus, ręce i nogi.
1 2 3 4 5 6 7 8 9 |
namespace DesignPatterns\Builder\Robot; interface RobotPlan { public function setHead($head); public function setTorso($torso); public function setArms($arms); public function setLegs($legs); } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 |
namespace DesignPatterns\Builder\Robot; use DesignPatterns\Builder\Robot\RobotPlan; class Robot implements RobotPlan { private $head; private $torso; private $arms; private $legs; public function setHead($head) { $this->head = $head; } public function getHead() { return $this->head; } public function setTorso($torso) { $this->torso = $torso; } public function getTorso() { return $this->torso; } public function setArms($arms) { $this->arms = $arms; } public function getArms() { return $this->arms; } public function setLegs($legs) { $this->legs = $legs; } public function getLegs() { return $this->legs; } } |
W dalszej kolejności tworzymy interfejs Budowniczego. W tym przypadku wywołuje on poszczególne metody Robota, ustawiające jego części oraz na końcu zwraca obiekt robota.
1 2 3 4 5 6 7 8 9 10 |
namespace DesignPatterns\Builder\Robot; interface RobotBuilder { public function buildHead(); public function buildTorso(); public function buildArms(); public function buildLegs(); public function getRobot(); } |
Następnie napiszemy konkretną klasę Budowniczego. Na potrzeby przykładu niech to będzie robot zbudowany z puszki aluminiowej o nazwie. Konkretny Budowniczy implementuje powyższy interfejs. Obiekt robota tworzony jest w konstruktorze.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
namespace DesignPatterns\Builder\Robot; use DesignPatterns\Builder\Robot\RobotBuilder; use DesignPatterns\Builder\Robot\Robot; class TinRobotBuilder implements RobotBuilder { private $robot; public function __construct() { $this->robot = new Robot; } public function buildHead() { $this->robot->setHead('aluminiowa kulka'); } public function buildTorso() { $this->robot->setTorso('puszka aluminiowa'); } public function buildArms() { $this->robot->setArms('zapałki'); } public function buildLegs() { $this->robot->setLegs('kółka'); } public function getRobot() { return $this->robot; } } |
Ostatnim elementem będzie napisanie Nadzorcy, którego w tym przypadku nazwałem Inżynierem. Zadaniem Inżyniera jest wybudowanie robota i zadbanie o cały proces (np. kolejność wywoływania odpowiednich metod).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
namespace DesignPatterns\Builder\Robot; class RobotEngineer { private $robotBuilder; public function __construct(RobotBuilder $robotBuilder) { $this->robotBuilder = $robotBuilder; } public function getRobot() { return $this->robotBuilder->getRobot(); } public function constructRobot() { $this->robotBuilder->buildHead(); $this->robotBuilder->buildTorso(); $this->robotBuilder->buildArms(); $this->robotBuilder->buildLegs(); } } |
Wszystkie elementy przykładu są już napisane. Na końcu trzeba w odpowiedni sposób użyć tak napisanego programu. Klient poniżej realizuje przebieg procesu budowania robota.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
use DesignPatterns\Builder\Robot\TinRobotBuilder; use DesignPatterns\Builder\Robot\RobotEngineer; $tinRobot = new TinRobotBuilder(); $robotEngineer = new RobotEngineer($tinRobot); $robotEngineer->constructRobot(); $robot = $robotEngineer->getRobot(); echo 'Budowa Robota:'; echo 'Głowa: '.$robot->getHead(); echo 'Korpus: '.$robot->getTorso(); echo 'Ręce: '.$robot->getArms(); echo 'Nogi: '.$robot->getLegs(); |
W pierwszej kolejności klient tworzy obiekt konkretnego Robota (Produktu), którego chce wybudować, a następnie tworzy obiekt Inżyniera (Nadzorcy) i przekazuje mu referencję do obiektu budowniczego, z którego ma korzystać. Następnie klient zleca skonstruowanie Produktu. Nadzorca zleca Budowniczemu wykonanie w odpowiedniej kolejności wszystkich czynności potrzebnych do utworzenia Produktu. Klient dostaje od Nadzorcy gotowy Produkt.
1 2 3 4 5 |
Budowa Robota: Głowa: aluminiowa kulka Korpus: puszka aluminiowa Ręce: zapałki Nogi: kółka |
Powyższy przykład można pobrać tutaj: robot-builder
Przykład na GitHub: https://github.com/molitorys/design-patterns/tree/master/src/Builder/Robot
Podsumowanie
Zaletą stosowania opisywanego wzorca jest oddzielenie procesu tworzenia obiektów od ich implementacji oraz duża możliwość zróżnicowania wewnętrznych struktur klas. Ponadto zwiększa się możliwość kontrolowania procesu tworzenia obiektów. Zwiększona jest również skalowalność, dodawanie nowych reprezentacji obiektów jest bardzo prosta.