MS Office week
Pouze tento týden sleva až 80 % na e-learning týkající se MS Office
50 % bodů zdarma na online výuku díky naší Slevové akci!

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");
Tento výukový obsah pomáhají rozvíjet následující firmy, které dost možná hledají právě tebe!

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.

Návrhový vzor Factory

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.


 

Všechny články v sekci
Návrhové vzory GoF
Článek pro vás napsal Patrik Valkovič
Avatar
Jak se ti líbí článek?
16 hlasů
Věnuji se programování v C++ a C#. Kromě toho také programuji v PHP (Nette) a JavaScriptu (NodeJS).
Aktivity (17)

 

 

Komentáře
Zobrazit starší komentáře (4)

Avatar
Milan Křepelka
Redaktor
Avatar
Milan Křepelka:24.11.2015 20:19

To je fluent interface. Lehce mimo záběř vytváření instancí. Navíc továrny mají šetřit práci a tohle mi teda moc nepřipadá. ;-)

 
Odpovědět
24.11.2015 20:19
Avatar
Honza Bittner
Redaktor
Avatar
Odpovídá na Milan Křepelka
Honza Bittner:24.11.2015 23:56

Koukám, že jsem si to zaměnil kvůli podobnosti. Googlil jsem si to a to, co jsem psal je označováno jako Builder.

Co jsem pochopil z googlení (např. http://stackoverflow.com/…7761/3281252) tak Factory umožní použít předpřipravené "nastavené" objektu. Oproti tomu Builder umožní jednoduché/přeh­ledné vytváření vlastního nastavení objektu.

Odpovědět
24.11.2015 23:56
Student FIT ČVUT. In love with Flutter. :-) Sleduj mě na https://twitter.com/tenhobi a ptej se na cokoli na https://g...
Avatar
Richard H.
Redaktor
Avatar
Richard H.:28.11.2015 23:01

Pěkný článek konečně mi došlo k čemu my je Factory.

Odpovědět
28.11.2015 23:01
Malý užitečný manuál je vždy lepší než bichle k ničemu.
Avatar
Erik Báča
Člen
Avatar
Erik Báča:13.4.2016 21:46

Proč je v těch ukázkách pořád string, místo String? Je to tam tolikrát, že to asi chyba není, ale nechápu to :D

Odpovědět
13.4.2016 21:46
Když mi dáš mínus, napiš proč!
Avatar
Taskkill
Redaktor
Avatar
Odpovídá na Erik Báča
Taskkill:13.4.2016 22:39

v c++ je to treba string, v Jave String v jS String atd ... ale rekl bych, ze nekdo ma navyk pouzivat string v pseudokodu... je to o preferenci rekl bych

 
Odpovědět
13.4.2016 22:39
Tento výukový obsah pomáhají rozvíjet následující firmy, které dost možná hledají právě tebe!
Avatar
Erik Báča
Člen
Avatar
Odpovídá na Taskkill
Erik Báča:13.4.2016 22:46

Aha, díky

Odpovědět
13.4.2016 22:46
Když mi dáš mínus, napiš proč!
Avatar
Odpovídá na Erik Báča
Patrik Valkovič:13.4.2016 23:17

v C# je string.

Odpovědět
13.4.2016 23:17
Nikdy neumíme dost na to, abychom se nemohli něco nového naučit.
Avatar
dave_23
Redaktor
Avatar
dave_23:14.6.2016 14:22

Neměla by u Vytváření instancí různých tříd být metoda Vytvor() statická? Následující příklad použití v programu tomu totiž nasvědčuje.

 
Odpovědět
14.6.2016 14:22
Avatar
David Čápka
Tým ITnetwork
Avatar
Odpovídá na dave_23
David Čápka:25.9.2017 10:32

Tam jen má být malé f, opravil jsem to :)

Odpovědět
25.9.2017 10:32
Jsem moc rád, že jsi na síti, a přeji ti top IT kariéru, ať jako zaměstnanec nebo podnikatel. Máš na to! :)
Avatar
Honza
Člen
Avatar
Honza:22. srpna 13:24

Neměla by být v následující inicializaci “TvarFactory” místo ”Factory”? Nejsem na PC abych si to moh vyzkoušet, ale předpokládám, ze žádný jazyk nemá generický typ Factory.

Factory faktorka = new TvarFactory();
 
Odpovědět
22. srpna 13:24
Děláme co je v našich silách, aby byly zdejší diskuze co nejkvalitnější. Proto do nich také mohou přispívat pouze registrovaní členové. Pro zapojení do diskuze se přihlas. Pokud ještě nemáš účet, zaregistruj se, je to zdarma.

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