V této části se podíváme na návrhový vzor jménem Builder. Jedná se o návrhový vzor, který slouží pro vytváření objektů.
Některé objekty jsou jednoduché a můžeme je vytvořit jednoduchým zavoláním konstruktoru. Pokud je ale tvorba objektu náročnější, tak si pro jeho vytváření můžeme vytvořit Builder. Použitím tohoto návrhového vzoru se vyhneme tomu, že by náš konstruktor obsahoval příliš mnoho parametrů ve kterých bychom se mohli začít ztrácet.
Builder představuje třídu, která obsahuje různé metody, které při vytváření objektu můžeme použít. Namísto předávání parametrů do konstruktoru tedy používáme tyto metody. Až jsme s tvorbou objektu hotovi, tak většinou zavoláme nějakou metodu Builderu, která nám vytvořený objekt vrátí.
První příklad použití Builderu, který si ukážeme, se nebude týkat přímo tvorby objektu. Budeme pomocí Builderu vytvářet HTML kód. Nejdříve si ale ukážeme, jak bychom mohli HTML kód vytvářet bez použití Builderu.
// VYTVOŘENÍ p ELEMENTU
let text = "Toto je odstavec.";
let html = [];
html.push("<p>");
html.push(text);
html.push("</p>");
// vypsání html kódu do konzole
console.log(html.join(""));
// VYTVOŘENÍ ul ELEMENTU
const polozky = ["návrhový", "vzor", "builder"];
html = [];
html.push("<ul>\n");
for (let polozka of polozky)
html.push(` <li>${polozka}</li>\n`);
html.push("</ul>");
// vypsání html kódu do konzole
console.log(html.join(""));
Určitě uznáte, že předchozí ukázka tvorby HTML kódu nevypadá vůbec dobře. Takto bychom HTML asi generovat nechtěli. Následující ukázka ukazuje, jak můžeme generovat HTML pomocí Builderu.
class Tag {
static VELIKOST_ODSAZENI = 2;
constructor(nazev="", text="") {
this.nazev = nazev;
this.text = text;
this.potomci = [];
}
// implementace pro získání HTML kódu tagu
toStringImpl(odsazeni) {
const html = [];
const mezery = " ".repeat(odsazeni * Tag.VELIKOST_ODSAZENI);
// počáteční tag
html.push(`${mezery}<${this.nazev}>\n`);
// text tagu
if (this.text.length > 0) {
html.push(" ".repeat(Tag.VELIKOST_ODSAZENI * (odsazeni+1)));
html.push(this.text);
html.push('\n');
}
// potomci tagu
for (let potomek of this.potomci)
html.push(potomek.toStringImpl(odsazeni+1));
// konečný tag
html.push(`${mezery}</${this.nazev}>\n`);
return html.join("");
}
// metoda pro získání HTML kódu tagu
toString() {
return this.toStringImpl(0);
}
// pomocí této statické metody můžeme získat nový Builder (nemusíme si jej tedy vytvářet sami)
static vytvor(nazev) {
return new HTMLBuilder(nazev);
}
}
// Builder pro tvorbu HTML (nebo vlastně Tag objektu)
class HTMLBuilder {
// při vytváření Builderu předáváme, jaký tag budeme vytvářet
constructor(rootNazev) {
this.root = new Tag(rootNazev);
this.rootNazev = rootNazev;
}
// pomocí této metody můžeme vytvářený objekt (this.root) nastavovat
pridejPotomka(nazev, text) {
// vytvářenému objektu se přidá nový potomek
const potomek = new Tag(nazev, text);
this.root.potomci.push(potomek);
// po provedení metody se builder vrací, takže můžeme řetězit další metody
return this;
}
// po nakonfigurování vytvářeného objektu můžeme zavolat tuto metodu a tím objekt získáme
postav() {
return this.root;
}
}
// vytvoření ul elementu pomocí Builderu
// - třída Tag obsahuje statickou metodu vytvor, která vrací nový HTMLBuilder (nemusíme si jej vytvářet sami)
const list = Tag.vytvor("ul")
.pridejPotomka("li", "návrhový")
.pridejPotomka("li", "vzor")
.pridejPotomka("li", "builder")
.postav(); // po konfiguraci objektu zavoláme metodu postav, která vytvořený objekt vrátí
// vypsání vytvořeného HTML kódu do konzole
console.log(list.toString());
Jak jste viděli v ukázce, s Builderem se HTML kód tvoří mnohem snadněji.
Občas může náš Builder být trochu složitější. V takovém případě si jej klidně můžeme rozdělit na více částí a v každé části stavět jinou část objektu. Jak to můžeme udělat ukazuje následující ukázka. V ukázce se nachází Builder pro vytváření objektů typu Clovek, který obsahuje gettery pro získání jiných Builderů, které slouží k postavení specifických částí objektu.
class Clovek {
constructor() {
this.jmeno = "";
// informace o adrese
this.adresa = this.postovniCislo = this.mesto = "";
// informace o zaměstnaní
this.jmenoSpolecnosti = this.pozice = "";
this.rocniPrijem = 0;
}
toString() {
return `${this.jmeno} žije v ${this.adresa}, ${this.postovniCislo}, ${this.mesto}\n` +
`a pracuje v ${this.jmenoSpolecnosti} jako ${this.pozice} s ročním příjmem ${this.rocniPrijem}.`;
}
}
// Builder pro objekty typu Clovek
class ClovekBuilder {
constructor(clovek=new Clovek()) {
this.clovek = clovek;
}
jmenujeSe(jmeno) {
this.clovek.jmeno = jmeno;
return this;
}
// getter k získání Builderu pro nastavení adresy člověka
get zije() {
return new ClovekAdresaBuilder(this.clovek);
}
// getter k získání Builderu pro nastavení zaměstnání člověka
get pracuje() {
return new ClovekZamestnaniBuilder(this.clovek);
}
// pomocí této metody získáme vytvořený objekt
postav() {
return this.clovek;
}
}
// Builder pro vlastnosti týkající se adresy objektu typu Clovek
class ClovekAdresaBuilder extends ClovekBuilder { // dědí se od třídy ClovekBuilder, takže se zdědí její metody
constructor(clovek) {
super(clovek);
}
v(adresa) {
this.clovek.adresa = adresa;
return this;
}
sPSC(postovniCislo) {
this.clovek.postovniCislo = postovniCislo;
return this;
}
veMeste(mesto) {
this.clovek.mesto = mesto;
return this;
}
}
// Builder pro vlastnosti týkající se zaměstnání objektu typu Clovek
class ClovekZamestnaniBuilder extends ClovekBuilder { // dědí se od třídy ClovekBuilder, takže se zdědí její metody
constructor(clovek) {
super(clovek);
}
v(jmenoSpolecnosti) {
this.clovek.jmenoSpolecnosti = jmenoSpolecnosti;
return this;
}
jako(pozice) {
this.clovek.pozice = pozice;
return this;
}
sPrijmem(rocniPrijem) {
this.clovek.rocniPrijem = rocniPrijem;
return this;
}
}
// vytvoření Builderu
const cb = new ClovekBuilder();
// vytvoření objektu typu Clovek s použitím Builderu
const karel = cb.jmenujeSe("Karel")
.zije.v("12 Londýnská").veMeste("New York").sPSC("123 45")
.pracuje.v("Koogle").jako("programátor").sPrijmem(420000)
.postav();
console.log(karel.toString());