Lekce 1 - Factory (tovární metoda)
V tomto kurzu si nejprve ukážeme programátorské návrhové vzory pro vytváření od skupiny GOF ze sekce Creational patterns. Patří mezi ně Factory, Builder, Prototype a Singleton. Dnes si ukážeme právě návrhový vzor Factory. Časem však probereme i vzory struktury a poté i chování.
Factory (nebo též počeštěně "faktorka") je jeden z nejdůležitějších návrhových vzorů, který umožňuje vyšší abstrakci při vytváření třídy než klasický konstruktor. Typicky se používá pro zapouzdření složitější inicializace instance a pro vytváření různých typů instancí podle řetězce.
Motivace
V aplikacích se nám občas stává, že potřebujeme vytvořit instanci nějaké třídy a tu dodatečně inicializovat. Dobrým praktickým příkladem jsou formulářové komponenty, u kterých nestačí pouze instanci vytvořit, ale musíme ji také nastavit spoustu dalších vlastností (rozměry, titulek, pozici, barvu...). Pokud někde v aplikaci vytváříte 20 podobných tlačítek a vytvoření takového tlačítka zabírá 10 řádků, nutně vás napadne oddělit tento kód do metody. Gratuluji, právě jste vynalezli factory. (samozřejmě má vzor nějaké další konvence)
Faktorka může také uchovávat proměnné, které potřebujeme k vytváření instancí. Tyto proměnné potom nemusí prostupovat celou aplikací. Další výhodou je návratový typ, které nemusí být u faktorky specifikován přesně na typ objektu, který vytváříme. Můžeme vracet některou z rodičovských tříd nebo i rozhraní. Na každý z těchto případů se podíváme blíže.
Vzor se nachází v různých podobách a existují i jeho další variace (factory, abstract factory, factory method, simple factory) a různé materiály je často vykládají různým způsobem. Základní myšlenka je ovšem vždy stejná.
Factory Method
Návrhový vzor Factory method využívá metody volající konstruktor. Má poměrně mnoho různých podob, někdy může být použito dědění a většinou se píše proti rozhraní. My si zde ukážeme úplně nejjednodušší implementaci. Klíčové je oddělení konstrukce konkrétní instance do jiné třídy, čímž se původní třída neznečistí konstrukčním kódem.
-
class Auto { private string znacka; private string model; public Auto(string znacka, string model) { this.znacka = znacka; this.model = model; } } class TovarnaNaAuta { public Auto VytvorFelicii() { return new Auto("Škoda", "Felicia"); } }
-
<?php class Auto { // od PHP verze 7.4 mohou mít vlastnosti třídy datové typy private string $znacka; private string $model; // od PHP verze 7 mohou mít parametry metod datové typy public function __construct(string $znacka, string $model) { $this->znacka = $znacka; $this->model = $model; } } class TovarnaNaAuta { // od PHP verze 7.1 může mít metoda datový typ public function VytvorFelicii() : Auto { return new Auto("Škoda", "Felicia"); } }
-
class Auto { constructor(znacka, model){ this.znacka = znacka; this.model = model; } } class TovarnaNaAuta { VytvorFelicii() { return new Auto("Škoda", "Felicia"); } }
Máme zde jednoduchou třídu s veřejným konstruktorem. Samozřejmě můžeme tvořit konkrétní instance automobilů:
-
Auto fabia = new Auto("Škoda", "Fabia");
-
$fabia = new Auto("Škoda", "Fabia");
-
fabia = new Auto("Škoda", "Fabia");
Jelikož v naší aplikaci často tvoříme Felicie nebo pro nás mají zkrátka nějaký vyšší význam a zároveň nechceme zasahovat do třídy Auto, je jejich konstrukce zjednodušena na pouhé zavolání metody tovární třídy:
-
TovarnaNaAuta tovarna = new TovarnaNaAuta(); Auto felicia = tovarna.VytvorFelicii();
-
$tovarna = new TovarnaNaAuta(); $felicia = $tovarna->VytvorFelicii();
-
tovarna = new TovarnaNaAuta(); felicia = tovarna.VytvorFelicii();
Když si představíte, že Felicie má automaticky nastavených např. 30 atributů, tak se vzor určitě vyplatí. A i kdybychom Felicii potřebovali jen na jednom místě v programu, oddělení složité inicializace do jiné třídy zpřehlední další logiku ve třídě, kde instanci potřebujeme.
Factory
Obecnějším návrhem je Factory. V principu se jedná opět o Factory Method, protože to musí být opět metoda, která naši instanci vytváří. Zde jsou požadavky mnohem volnější. Metoda může být definována jako statická (někdy i v té samé třídě, s tím se setkáváme typicky u Javy). Ukažme si další příklad:
-
class Auto { private string znacka; private string model; private Auto(string znacka, string model) { this.model = model; this.znacka = znacka; } public static Auto Felicia() { return new Auto("Škoda", "Felicia"); } }
-
class Auto { // od PHP verze 7.4 mohou mít vlastnosti třídy datové typy private string $znacka; private string $model; // od PHP verze 7 mohou mít parametry metod datové typy public function __construct(string $znacka, string $model) { $this->znacka = $znacka; $this->model = $model; } // od PHP verze 7.1 může mít metoda datový typ public static function Felicia() : Auto { return new Auto("Škoda", "Felicia"); } }
-
class Auto { constructor(znacka, model){ this.znacka = znacka; this.model = model; } static Felicia() { return new Auto("Škoda", "Felicia"); } }
V této variantě vzoru se instance třídy nedá vytvořit žádným jiným způsobem, než tovární metodou (ale samozřejmě může být konstruktor i veřejný).
Instanci vytvoříme jako:
-
Auto felicia = Auto.Felicia();
-
$felicia = Auto::Felicia();
-
felicia = Auto.Felicia();
Výhodou statické metody přímo ve třídě je jednodušší implementace. Samozřejmě bychom jich neměli mít ve třídě moc a měly by být nějak vázané na původní funkcionalitu třídy, jinak by měly být v samostatné třídě. Asi nejsprávnějším příkladem takové statické metody je získání aktuálního data a času v C# .NET:
-
DateTime listopad = new DateTime(2020, 11, 24); // Konkrétní datum DateTime dnes = DateTime.Now();
-
$listopad = new DateTime("2020-11-24"); // PHP nemá metodu Now() $dnes = date_create();
-
listopad = new Date("2020-11-24"); dnes = new Date.now();
Metoda Now()
vrátí instanci DateTime
,
inicializovanou na aktuální datum a čas. Taková metoda přímo souvisí s
funkcionalitou DateTime
a proto je návrhově správně, že je v
této třídě a i statika zde dává smysl. Naopak u naší
Felicie()
je to spíše odstrašující příklad, protože s
obecnou třídou Auto
příliš nesouvisí.
Pozn.: V C# .NET je Now()
vlastnost, která se od metody v
nějakých detailech odlišuje, v příkladu byla uvedena jako metoda, aby
zbytečně nezmátla programátory v jiných jazycích.
Vytváření instancí různých tříd
Návratová hodnota nemusí být u Faktory stejná,
jako je typ vytvářené instance. Klidně můžeme vytvářet při každém
volání instanci jiné typ třídy a vracet pouze rozhraní, které všechny
třídy implementují. Jako příklad uvedu grafický program, který vykresluje
různé obrazce na obrazovku. Řekněme tedy, že máme čtverec, kruh a
trojúhelník, které všechny implementují rozhraní
IVykreslitelny
. Data získáváme z řetězce (např. parsujeme
nějaký textový soubor). Podle dodaných dat se rozhodneme, který typ tvaru
vytvořit a vrátíme jej pouze jako rozhraní. Samotný program neví, co je to
za obrazec. Pouze ví, že jej lze vykreslit.
-
interface IVykreslitelny { void Vykresli(); } class Ctverec : IVykreslitelny { /* ... Kód ... */ } class Trojuhelnik : IVykreslitelny { /* ... Kód ... */ } class Kruh : IVykreslitelny { /* ... Kód ... */ } class TvarFactory { public IVykreslitelny Vytvor(string typ) { if (typ == "Ctverec") return new Ctverec(); else if (typ == "Trojuhelnik") return new Trojuhelnik(); else if (typ == "Kruh") return new Kruh(); } } // použití v programu TvarFactory faktorka = new TvarFactory(); IVykreslitelny instance = faktorka.Vytvor("Ctverec");
-
interface IVykreslitelny { public function Vykresli(); } class Ctverec implements IVykreslitelny { /* ... Kód ... */ } class Trojuhelnik implements IVykreslitelny { /* ... Kód ... */ } class Kruh implements IVykreslitelny { /* ... Kód ... */ } class TvarFactory { // Návratový typ metody je možný od verze 7.1 public function Vytvor(string $typ) : IVykreslitelny { if ($typ == "Ctverec") return new Ctverec(); else if ($typ == "Trojuhelnik") return new Trojuhelnik(); else if ($typ == "Kruh") return new Kruh(); } } // použití v programu $faktorka = new TvarFactory(); $instance = $faktorka->Vytvor("Ctverec");
-
// JS nemá interface, můžeme to však alternativně zajistit takto: function TvarFactory() { this.vytvor = function (typ) { if (typ === "Ctverec") { return new Ctverec(); } else if (typ === "Trojuhelnik") { return new Trojuhelnik(); } else if (typ === "Kruh") { return new Kruh(); } } } let Ctverec = function () { /* ... Kód ... */ }; let Trojuhelnik = function () { /* ... Kód ... */ }; let Kruh = function () { /* ... Kód ... */ }; // použití v programu let faktorka = new TvarFactory(); let instance = faktorka.vytvor("Ctverec");
Vidíme, že typ se mění. Jediné, co program ví, je, že lze objekt vykreslit, ale již nic o tom jak se vykreslí. To by jej ale nemělo ani zajímat, protože by se o vykreslení starat neměl. Buď se o vykreslení stará opět konkrétní třída nebo samotné objekty.
Pokud by tvarů bylo více typů, je výhodnější použít reflexi a dynamicky vytvářet instance tříd, jejichž název koresponduje s názvem tvaru. Nesmíme zde zapomenout nějak ošetřit, aby bylo vytváření tříd omezeno na určitý balíček, jinak by si uživatel mohl vytvořit prakticky cokoli z naší aplikace.

Závislost na parametrech
Někdy potřebujeme vytvořit instanci třídy, ale parametry pro konstruktor získáme na jiném místě v programu. Konstruktor využít nemůžeme, protože ještě neznáme všechny parametry a vytvořit neinicializovanou instanci např. bezparametrickým konstruktorem není dobrá praktika. Pokud využijeme Factory třídu, můžeme jí data postupně předávat a ona si je bude uchovávat. Ve chvíli, kdy dat bude dostatek, můžeme instanci vytvořit. Nemusíme se tedy starat o žádné předávání dat mezi částmi programu, předáme pouze instanci třídy Factory.
Do tohoto článku bude postupně doplňována další implementace různých typů továrniček. Chcete nám pomocí svou zkušeností s factory? Napište nám do komentářů.
V další lekci, Singleton (jedináček), si ukážeme populární návrhový vzor Singleton umožňující globální přístup k instanci nějaké třídy. Ukážeme si výhody i nevýhody a zkusíme si ho i naprogramovat.
Komentáře


Zobrazeno 10 zpráv z 15. Zobrazit vše