Builder

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.

Obecný popis

[editovat | editovat zdroj]

„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]

Stručná historie

[editovat | editovat zdroj]

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).

Pohled programátora

[editovat | editovat zdroj]

Motivace užití

[editovat | editovat zdroj]

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.

Diagram
Diagram

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.

Concrete Builder

[editovat | editovat zdroj]

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]

Příklad užití

[editovat | editovat zdroj]

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.

Výsledek užití

[editovat | editovat zdroj]

„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]

Užitečné tipy při užití

[editovat | editovat zdroj]
  • Builder slouží ke konstrukci složitých objektů po jednotlivých krocích. Zatímco Abstraktní továrna se používá při tvorbě blízké skupiny objektů (jednoduchých i komplexních). Abstraktní továrna vrací produkt v jednom kroku, u vzoru Builder je výsledný objekt vrácen po ukončení a jako výsledek konstrukce.
  • Návrhový vzor Stavitel se obvykle používá k tvorbě objektů typu Kompozit.
  • Tam kde je třeba dodatečná flexibilita, dochází v návrhu k vývoji od návrhového vzoru Továrna, přes, či k Abstraktní továrně, Prototypu a Staviteli. Tedy od jednodušších, snáze přizpůsobitelných vzorů s možností tvorby podtříd k pružnějším, ale složitějším vzorům.
  • Vytvářející návrhové vzory se mohou vzájemně doplňovat. Například Stavitel může k určení, který objekt se má vytvořit Abstraktní továrnu, nebo jiný návrhový vzor. Dále Abstraktní továrna, Stavitel i Prototyp mohou použít Jedináčka (Singleton) ve svých implementacích.
  • Vzor Stavitel je dobrým kandidátem pro implementaci plynulého rozhraní (řetězcovité volání - fluent interface).

Příklady Implementace

[editovat | editovat zdroj]
 /** "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

Bloch‘s Builder

[editovat | editovat zdroj]

Ú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ů.

Související vzory

[editovat | editovat zdroj]
  • Composite – vytvářený objekt pomocí Builderu je obvykle Composite.
  • Factory Method – využití vzoru k implementaci rozhodnutí, o vytvoření ConcreteBuilder.
  • Interpret – využití jako „parseru“.
  1. Gang of Four Content Creation Wiki for People Projects And Patterns in Software Development.
  2. Alexander, Christopher; Sara Ishikawa, Murray Silverstein, Max Jacobson, Ingrid Fiksdahl-King, Shlomo Angel (1977). A Pattern Language: Towns, Buildings, Construction. New York: Oxford University Press. ISBN 978-0-19-501919-3.
  3. Joshua, Bloch (2008). Effective Java. Addison-Wesley Java series. ISBN 978-0-321-35668-0.
  4. a b c Miloš Dvořák. Návrhové vzory Archivováno 2. 9. 2012 na Wayback Machine.

Literatura

[editovat | editovat zdroj]

Související články

[editovat | editovat zdroj]

Externí odkazy

[editovat | editovat zdroj]