Factory (tovární metoda)

Návrhové vzory Factory (tovární metoda)

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)

Dále nám může faktorka uchovávat proměnné, které potřebujeme k vytváření objektu. Tyto proměnné potom nemusí prostupovat celým programem. 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 faktorů se podíváme blíže.

Pozn.: 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 neznečistí třída Auto.

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");
        }
}

Máme zde jednoduchou třídu s veřejným konstruktorem. Můžeme tvořit konkrétní instance automobilů:

Auto 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();

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");
        }
}

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();

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(2015, 11, 24); // Konkrétní datum
DateTime dnes = DateTime.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 kvůli programátorům v jiných jazycích uvedena jako metoda.

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í 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
Factory Faktorka = new TvarFactory();
IVykreslitelny InstanceNasiTridy = 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.

Pozn.: 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.

Factory

Závislost na parametrech

Někdy potřebujeme vytvořit třídu podle parametrů, které dostaneme až na jiném místě v programu. Konstruktor využít nemůžeme, protože všechny parametry ještě neznáme 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 Factory třídu.

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ářů.


 

  Aktivity (5)

Článek pro vás napsal patrik.valkovic
Avatar
Věnuji se programování v C++ a C#. Kromě toho také programuji v PHP (Nette) a JavaScriptu.

Jak se ti líbí článek?
Celkem (5 hlasů) :
4.84.84.84.84.8


 


Miniatura
Předchozí článek
Object pool (fond objektů)
Miniatura
Všechny články v sekci
Návrhové vzory
Miniatura
Následující článek
Immutable objects (neměnné objekty)

 

 

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

Avatar
Milan Křepelka
Redaktor
Avatar
Milan Křepelka:

Takhle prznit mateřský jazyk. Fuj. Přitom termín faktorka se nikde nepoužívá. Tuhle nemoc si chytil od paka ne? Ten taky s oblibou používá takový připitomělý výrazivo.

 
Odpovědět 24.11.2015 20:00
Avatar
Jan Vargovský
Redaktor
Avatar
Jan Vargovský:

To je method chaining pattern.

 
Odpovědět 24.11.2015 20:06
Avatar
Milan Křepelka
Redaktor
Avatar
Milan Křepelka:

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  +1 24.11.2015 20:19
Avatar
Honza Bittner
Redaktor
Avatar
Odpovídá na Milan Křepelka
Honza Bittner:

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  +1 24.11.2015 23:56
Ptejte se mě na cokoli na https://github.com/HoBi/ama a followujte mě na Twitteru https://twitter.com/tenhobi. :-)
Avatar
Richard H.
Redaktor
Avatar
Richard H.:

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:

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. dubna 21:46
Když mi dáš mínus, napiš proč!
Avatar
Taskkill
Redaktor
Avatar
Odpovídá na Erik Báča
Taskkill:

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  +1 13. dubna 22:39
Avatar
Erik Báča
Člen
Avatar
Odpovědět 13. dubna 22:46
Když mi dáš mínus, napiš proč!
Avatar
patrik.valkovic
Šéfredaktor
Avatar
Odpovědět 13. dubna 23:17
Nikdy neumíme dost na to, abychom se nemohli něco nového naučit.
Avatar
dave_23
Člen
Avatar
dave_23:

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. června 14:22
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 12. Zobrazit vše