Návrhový vzor stavitel (anglicky Builder) je softwarový návrhový vzor. Patří do skupiny vytvářejících (creational) návrhových vzorů.
Slouží k abstrahování tvorby složitých objektů. A to tak, aby stejné výrobní schéma mohlo být použito pro tvorbu různých objektů. Způsob konstrukce těchto objektů je tedy stejný, jednotlivé kroky se však liší. Často se používá společně s návrhovým vzorem Kompozit (Composite), konkrétně vytvářené objekty mohou být typu kompozit, dle zmíněného vzoru.
„Návrhový vzor oddělující konstrukci složitých objektů od jejich reprezentace. Čímž je možné použít stejný proces konstrukce pro rozdílné reprezentace.“[1]
První publikace popisující myšlenku návrhových vzorů pochází z roku 1977.[2] Název „builder pattern“ se však v odborné literatuře před rokem 1995 a zveřejnění knihy návrhové vzory nevyskytuje. Vzorem se dále zabývá Josua Bloch ve své knize Effective Java 5.[3] Kdy navrhuje alternativu zaměřenou na odstranění opakování kódu v třídách Concrete Builder (viz Struktura).
Použití Stavitele je vhodné u rozdílných tříd s podobným procesem konstrukce. Samotné třídy mohou být rozdílné a nezávislé. Tím se liší od Abstraktní továrny, která slouží k tvorbě podobných objektů. Podobných z hlediska prezentace v systému Windows, nebo Mac. Důvodem pro použití je oddělení vytváření složitých objektů od jejich prezentace, které by mělo vést k přehlednějšímu kódu.
Třída řídící proces vytváření objektů.
„Instance třídy Director získává od klienta určení, který Builder má použít. Tím je i definováno, který produkt bude vyráběn. Director řídí vytvoření produktu voláním metod pro zhotovení jednotlivých částí z rozhraní Builder. Po vytvoření požadované části výsledného produktu je tato do něho začleněna. Výsledný produkt je možné získat použitím metody getResult. Director je odstíněn od způsobu, jakým se vytvářejí konkrétní části a jak se skládají. Určuje, ovšem kdy se mají části vyrobit a tím řídí proces vytváření produktů.“[4]
Abstraktní rozhraní pro tvorbu objektů (produkt). Metoda buildPart slouží k vytvoření konkrétní části objektu, o její volání se stará Director.
Implementace rozhraní Builder schopná vytvářet další objekty. Vytváří a sestavuje součásti pro tvorbu složitých objektů. Metoda getResult slouží k předání výsledného produktu a volá ji zpravidla klient, který konstrukci objektu vyvolal.
„Která z možných tříd ConcreteBuilder je použita pro vytvoření objektu, může být rozhodnuto na základě použitých dat, která mají být zobrazena. Například pokud má být zobrazeno několik položek, je klientu předán jako Product seznam tvořen „checkBoxy“. V případě více položek bude vytvořen a předán rozbalovací seznam. Samozřejmě kromě zobrazovaných dat mohou hrát roli i jiné faktory. Directoru je předán jako parametr instance ConcreteBuilder a Director, s využitím metod nadefinovaných v rozhraní, řídí proces vytváření produktu.“[4]
Konstrukci provádí konkrétní implementace rozhraní Builder, ale proces konstrukce řídí třída Director. Například klient přijde k automatu a chce kapučíno. O tvorbu kapučína se stará automat (direktor), klient si zvolí nápoj stiskem tlačítka. Automat provádí vždy ty samé kroky, existuje tedy jediné výrobní schéma, ale podle stisku tlačítka mohou být některé kroky vynechány (cukr), některé vzájemně zaměnitelné (kapučíno vs. čaj do kelímku). O realizaci těchto jednotlivých kroků se starají různé implementace třídy Builder (Stavitel čajů, stavitel kafí, apod.). Nápoj je možné odebrat teprve poté, kdy jsou všechny kroky konstrukce dokončeny.
Tedy zjednodušeně: Ke konstrukci jednoho objektu slouží právě jeden Builder, proces konstrukce řídí Direktor, objekt ke konstrukci vybírá klient předáním Builderu Direktoru, po ukončení konstrukce si z Builderu pouze vybere výsledek metodou getResult.
Stavitelů ke konstrukci jednoho objektu může být voláno i více. Například pokud je výsledný produkt Composite může být při jeho konstrukci voláno více stavitelů, případně stejný stavitel několikrát za sebou. Tak je zajištěno například vytváření složitých objektů grafického rozhraní operačního systému.
„Je vytvořen princip konstrukce objektů, který je variabilní na základě používaných dat nebo nadefinování věcné logiky. Klient je odstíněn od tvorby objektů a pouze prezentuje výsledek.“[4]
/** "Product" */
class Pizza {
private 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; }
}
/** "Abstract Builder" */
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();
}
/** "ConcreteBuilder" */
class HawaiianPizzaBuilder extends PizzaBuilder {
public void buildDough() { pizza.setDough("cross"); }
public void buildSauce() { pizza.setSauce("mild"); }
public void buildTopping() { pizza.setTopping("ham+pineapple"); }
}
/** "ConcreteBuilder" */
class SpicyPizzaBuilder extends PizzaBuilder {
public void buildDough() { pizza.setDough("pan baked"); }
public void buildSauce() { pizza.setSauce("hot"); }
public void buildTopping() { pizza.setTopping("pepperoni+salami"); }
}
/** "Director" */
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();
}
}
/** A customer ordering a pizza. */
class BuilderExample {
public static void main(String[] args) {
Waiter waiter = new Waiter();
PizzaBuilder hawaiianPizzaBuilder = new HawaiianPizzaBuilder();
PizzaBuilder spicyPizzaBuilder = new SpicyPizzaBuilder();
waiter.setPizzaBuilder(hawaiianPizzaBuilder);
waiter.constructPizza();
Pizza pizza = waiter.getPizza();
}
}
using System;
namespace BuilderPattern
{
// Builder - abstract interface for creating objects (the product, in this case)
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();
}
// Concrete Builder - provides implementation for Builder; an object able to construct other objects.
// Constructs and assembles parts to build the objects
class HawaiianPizzaBuilder : PizzaBuilder
{
public override void BuildDough()
{
pizza.Dough = "cross";
}
public override void BuildSauce()
{
pizza.Sauce = "mild";
}
public override void BuildTopping()
{
pizza.Topping = "ham+pineapple";
}
}
// Concrete Builder - provides implementation for Builder; an object able to construct other objects.
// Constructs and assembles parts to build the objects
class SpicyPizzaBuilder : PizzaBuilder
{
public override void BuildDough()
{
pizza.Dough = "pan baked";
}
public override void BuildSauce()
{
pizza.Sauce = "hot";
}
public override void BuildTopping()
{
pizza.Topping = "pepperoni + salami";
}
}
// Director - responsible for managing the correct sequence of object creation.
// Receives a Concrete Builder as a parameter and executes the necessary operations on it.
class Cook
{
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();
}
}
// Product - The final object that will be created by the Director using Builder
public class Pizza
{
public string Dough = string.Empty;
public string Sauce = string.Empty;
public string Topping = string.Empty;
}
class Program
{
static void Main(string[] args)
{
PizzaBuilder hawaiianPizzaBuilder = new HawaiianPizzaBuilder();
Cook cook = new Cook();
cook.SetPizzaBuilder(hawaiianPizzaBuilder);
cook.ConstructPizza();
// create the product
Pizza hawaiian = cook.GetPizza();
PizzaBuilder spicyPizzaBuilder = new SpicyPizzaBuilder();
cook.SetPizzaBuilder(spicyPizzaBuilder);
cook.ConstructPizza();
// create another product
Pizza spicy = cook.GetPizza();
}
}
}
#include <string>
#include <iostream>
using namespace std;
// "Product"
class Pizza {
public:
void dough(const string& dough) {
dough_ = dough;
}
void sauce(const string& sauce) {
sauce_ = sauce;
}
void topping(const string& topping) {
topping_ = topping;
}
void open() const {
cout << "Pizza with " << dough_ << " dough, " << sauce_ << " sauce and "
<< topping_ << " topping. Mmm." << endl;
}
private:
string dough_;
string sauce_;
string topping_;
};
// "Abstract Builder"
class PizzaBuilder {
public:
const Pizza& pizza() {
return pizza_;
}
virtual void buildDough() = 0;
virtual void buildSauce() = 0;
virtual void buildTopping() = 0;
protected:
Pizza pizza_;
};
//----------------------------------------------------------------
class HawaiianPizzaBuilder : public PizzaBuilder {
public:
void buildDough() {
pizza_.dough("cross");
}
void buildSauce() {
pizza_.sauce("mild");
}
void buildTopping() {
pizza_.topping("ham+pineapple");
}
};
class SpicyPizzaBuilder : public PizzaBuilder {
public:
void buildDough() {
pizza_.dough("pan baked");
}
void buildSauce() {
pizza_.sauce("hot");
}
void buildTopping() {
pizza_.topping("pepperoni+salami");
}
};
//----------------------------------------------------------------
class Cook {
public:
Cook()
: pizzaBuilder_(nullptr)
{ }
~Cook() {
if (pizzaBuilder_)
delete pizzaBuilder_;
}
void pizzaBuilder(PizzaBuilder* pizzaBuilder) {
if (pizzaBuilder_)
delete pizzaBuilder_;
pizzaBuilder_ = pizzaBuilder;
}
const Pizza& getPizza() {
return pizzaBuilder_->pizza();
}
void constructPizza() {
pizzaBuilder_->buildDough();
pizzaBuilder_->buildSauce();
pizzaBuilder_->buildTopping();
}
private:
PizzaBuilder* pizzaBuilder_;
};
int main() {
Cook cook;
cook.pizzaBuilder(new HawaiianPizzaBuilder);
cook.constructPizza();
Pizza hawaiian = cook.getPizza();
hawaiian.open();
cook.pizzaBuilder(new SpicyPizzaBuilder);
cook.constructPizza();
Pizza spicy = cook.getPizza();
spicy.open();
}
<?php
class Pizza {
protected $dough;
protected $sauce;
protected $topping;
public function setDough($dough) {
$this->dough = $dough;
}
public function setSauce($sauce) {
$this->sauce = $sauce;
}
public function setTopping($topping) {
$this->topping = $topping;
}
public function showIngredients() {
echo "Dough : ".$this->dough."<br/>";
echo "Sauce : ".$this->sauce."<br/>";
echo "Topping : ".$this->topping."<br/>";
}
}
abstract class PizzaBuilder {
protected $pizza;
public function getPizza() {
return $this->pizza;
}
public function createNewPizzaProduct() {
$this->pizza = new Pizza();
}
public abstract function buildDough();
public abstract function buildSauce();
public abstract function buildTopping();
}
class HawaiianPizzaBuilder extends PizzaBuilder {
public function buildDough() {
$this->pizza->setDough("cross");
}
public function buildSauce() {
$this->pizza->setSauce("mild");
}
public function buildTopping() {
$this->pizza->setTopping("ham + pineapple");
}
}
class SpicyPizzaBuilder extends PizzaBuilder {
public function buildDough() {
$this->pizza->setDough("pan baked");
}
public function buildSauce() {
$this->pizza->setSauce("hot");
}
public function buildTopping() {
$this->pizza->setTopping("pepperoni + salami");
}
}
class Waiter {
protected $pizzaBuilder;
public function setPizzaBuilder(PizzaBuilder $pizzaBuilder) {
$this->pizzaBuilder = $pizzaBuilder;
}
public function getPizza() {
return $this->pizzaBuilder->getPizza();
}
public function constructPizza() {
$this->pizzaBuilder->createNewPizzaProduct();
$this->pizzaBuilder->buildDough();
$this->pizzaBuilder->buildSauce();
$this->pizzaBuilder->buildTopping();
}
}
class Tester {
public static function main() {
$oWaiter = new Waiter();
$oHawaiianPizzaBuilder = new HawaiianPizzaBuilder();
$oSpicyPizzaBuilder = new SpicyPizzaBuilder();
$oWaiter->setPizzaBuilder($oHawaiianPizzaBuilder);
$oWaiter->constructPizza();
$pizza = $oWaiter->getPizza();
$pizza->showIngredients();
echo "<br/>";
$oWaiter->setPizzaBuilder($oSpicyPizzaBuilder);
$oWaiter->constructPizza();
$pizza = $oWaiter->getPizza();
$pizza->showIngredients();
}
}
Tester::main(); output : Dough : cross Sauce : mild Topping : ham + pineapple Dough : pan baked Sauce : hot Topping : pepperoni + salami
Úprava návrhového vzoru stavitel prezentovaná Josuou Blochem by neměla sloužit jako nezbytná náhrada pro původní návrhový vzor. Jejím cílem je vyřešit problém s příliš velkým množstvím konstruktorů, jejich parametrů a naduživání seterů (přiřazení hodnoty) při vytváření objektů.
Velké množství parametrů v konstruktoru, obzvláště jsou-li volitelné, je nepřehledné a těžko použitelné. Problém vzniká před předáním objektu typu builder Direktorovi. Uživatel je nucen builder vytvořit a předat mu hodnoty, které by měl budoucí objekt nést. Kód se, ale výrazně zpřehlední, když výběr Builderu a tvorbu objektu provedeme v jednom kroku.
Například pro Javu:
Objekt1 x = new Objekt1.Builder(“Jméno“,10)
.cast1(“udaj1“).build();
Objekt1 y = new Objekt1.Builder(“Jméno2“,3).
cast1(“udaj1“).
cast2(“udaj1“,“udaj2“).build();
objekty x a y jsou tedy dva objekty stejného typu, kde v prvním objektu nebyla vytvořena druhá část, to však nebrání jeho použití.
Kostra řešení by byla následující:
public class Objekt1 { public static class Builder { public Builder(String jmeno, int cislo) {…} public Object1 build() { return new Objekt1(this);} public Builder cast1(String hodnota) { … return this; } public Builder cast2(String hodnota1, String hodnota2) { … return this; } } private Objekt1(Builder builder) { jmeno = builder.jmeno; cislo = builder.cislo; hodnota = builder.hodnota; ... ... } }
Takové řešení je navrženo primárně pro Javu a pro jiné jazyky musí být dále upravováno. Dále taky může být rozšířeno a vylepšeno použitím Generických parametrů.