Nahoru
 

Abstraktní továrna

Co odlišuje dobrého programátora od někoho, kdo jen „naťuká“ kód? Je to způsob, jakým práci provede. Jak bude kód čitelný, rozšiřitelný, testovatelný a podobně, na tom všem závisí výsledná kvalita produktu. Právě zde přichází do hry návrhové vzory, které pomáhají programátorům s řešením známých problémů.

Abstraktní továrna patří mezi návrhové vzory rodiny GoF (Gang of Four patterns). Jedná se o vytvářecí návrhový vzor, který se používá pro instancování tříd. Jeho jádrem je specifikace tovární třídy.

Pokud vám následující definice přijde složitá, shlédněte nejprve připravený příklad.

Představme si, že stojíme před problémem, kdy musíme vytvářet různé objekty v závislosti na prostředí, ale veškerou ostatní logiku chceme ponechat stejnou. Například vytváříme vědeckou kalkulačku, která umí spočítat cokoliv a chceme, aby ji bylo možné spustit na různých prostředích. Jinými slovy potřebujeme, aby měla aplikace různá GUI (grafická uživatelská rozhraní). Logiku však měnit nechceme. Přesně v tomto případě by mohla na scénu přijít abstraktní továrna, která daný problém umí vyřešit, pouhým dosazením správné tovární třídy.

Konkrétní implementace továrny je dosazena až za běhu programu.

Jak tedy návrhový vzor abstraktní továrny vypadá? V první řadě máme nějaký produkt (objekt), který chceme vytvořit. Pro tento produkt vytvoříme abstraktní třídu. Té budeme říkat abstraktní produkt. Jedná se o to, co chceme vyrobit. Nyní potřebujeme vytvořit továrnu, která nám daný objekt poskytne (bude tvořit). Připravíme abstraktní továrnu, která bude specifikovat, jaké rozhraní budou muset mít všechny továrny z ní dědící. Jednotlivé konkrétní továrny dědící z abstraktní továrny už budou obsahovat reálnou implementaci, a právě ty budeme za běhu různě dosazovat, abychom dostali náš kýžený produkt pro danou situaci.

UML Diagram pro obecnou abstraktní továrnu
Obrázek č. 1: UML Diagram obecné abstraktní továrny

Pojďme si ukázat reálné použití zjednodušené abstraktní továrny na příkladu. Představme si, že vyvíjíme hru, kde jsou lidé a orkové a nacházíme se v situaci, kdy potřebujeme vytvořit skupinu sta orků a lidí v náhodném rozpoložení. Orkové a lidé budou představováni objekty Orc a Human. Aby to však nebylo tak jednoduché, tak se naše společnost rozhodla přidat do hry krvavý mód, který změní jak grafiku, tak obtížnost celé hry. Jakým způsobem to hru změní, nás v tomto případě nezajímá, ale musíme nějak zajistit, aby bylo přepínání z normálního módu do krvavého, co nejednoduší. Chceme se vyhnout duplicitám kódu a nerozšiřitelnosti.

Následující příklad je napsaný v programovacím jazyce C#.

Pojďme si ujasnit, jak zakomponujeme náš příklad do světa abstraktní továrny. Co chceme vyrábět – neboli jaký bude náš produkt? Naším produktem budou lidé a orkové. V závislosti na zapnutém módu je potřeba vyrobit různé objekty. Vytvoříme jim tedy abstraktního předka, který se bude jmenovat kreatura (Creature) a ten nazveme abstraktním produktem.

Dále potřebujeme definovat továrny. Pro normální mód vytvoříme klasickou továrnu (ClassicFactory) a pro krvavý mód krvavou továrnu (BloodyFactory). Pro ně je potřeba definovat společné rozhraní, kde si v tomto případě vystačíme s rozhraním ICreatureFactory.

UML Diagram pro náš příklad
Obrázek č. 2: UML Diagram našeho příkladu

Kód pro naše lidi a orky by mohl vypadat následovně. Definujeme abstraktní třídu Creature a z ní necháme dědit třídy Human a Orc.

// nas abstraktni produkt
abstract class Creature
{
    public int Health;
    public int Damage;

    public abstract void Spawn();

    // dalsi metody
}

// konkretni produkt 'clovek'
class Human : Creature
{
    public override void Spawn()
    {
        // implementace zrozeni cloveka
    }

    // dalsi metody
}

// konkretni produkt 'ork'
class Orc : Creature
{
    public override void Spawn()
    {
        // implementace zrozeni orka
    }

    // dalsi metody
}

Prozatím se jedná o jednoduchý kód, který toho zase tak moc neumí. Dále potřebujeme udělat továrny, které nám dané objekty budou vytvářet. Konečně tedy vytvoříme rozhraní pro abstraktní továrnu a z ní necháme dědit konkrétní továrny, v našem případě klasickou a krvavou.

// rozhrani abstraktni tovarny
interface ICreatureFactory
{
    Creature CreateOrc();
    Creature CreateHuman();
}

// konkretni tovarna pro normalni mod
class ClassicFactory : ICreatureFactory
{
    public Creature CreateOrc()
    {
        Console.WriteLine("Vytvarim klasickeho orka");
        return new Orc();       
    }

    public Creature CreateHuman()
    {
        Console.WriteLine("Vytvarim klasickeho cloveka");
        return new Human();
    }
}

// konkretni tovarna pro krvavy mod
class BloodyFactory : ICreatureFactory
{
    public Creature CreateOrc()
    {
        Orc orc = new Orc();

        // pridej neco krvaveho

        Console.WriteLine("Vytvarim krvaveho orka");

        return orc;
    }

    public Creature CreateHuman()
    {
        Human human = new Human();

        // pridej neco krvaveho

        Console.WriteLine("Vytvarim krvaveho cloveka");

        return human;
    }
}

V tuto chvíli máme hotovo a vyřešili jsme problém s odstíněním výroby správných objektů od zbytku programu. Naše příkladová implementace samozřejmě reálně nic neimplementuje, ale pouze ukazuje postup, jak návrhový vzor abstraktní továrna použít. V hlavní smyčce programu bychom potom mohli na základě nějakého nastavení použít buď klasickou nebo krvavou továrnu. Zároveň se po nás chtělo vytvoření sta orků a lidí v náhodném rozpoložení. To řeší následující kód.

public void Game()
{
    ICreatureFactory factory;

    // zjisti z nastaveni, zda jsme v krvavem modu
    bool bloody = IsBloody();

    // podle hodnoty v nastaveni vytvor prislusnou tovarnu
    if (bloody)
    {
        factory = new BloodyFactory();
    }
    else
    {
        factory = new ClassicFactory();
    }

    // nyni muzu operovat s kreaturami pres rozhrani jak chci
    // a je mi jedno jakou tovarnou byly vyrobeny

    // vygeneruj 100 kreatur, kde jsou orci a lide v nahodnem zastoupeni
    Creature[] creatures = new Creature[100];
    for (int i = 0; i < 100; i++)
    {
        if (RandomBool())
            creatures[i] = factory.CreateOrc();
        else
            creatures[i] = factory.CreateHuman();
    }


    // pokracovani programu hry
}

Abychom dodrželi vzor abstraktní továrny naprosto přesně, museli bychom jak pro orka, tak pro člověka udělat speciální abstraktní třídu, z níž by byly odděděny třídy KlasickyOrk, KrvavyOrk, KlasickyClovek a KrvavyClovek. Pro zjednodušení však tuto úroveň abstrakce vynecháváme.

Abstraktní továrna patří mezi velmi používané návrhové vzory. Společně s ostatními GoF vzory byl tento vzor vymyšlen programátory, kteří mají ve svém oboru spousty zkušeností. Použitím těchto návrhových vzorů si ušetříme spoustu času vymýšlením toho nejlepšího řešení.

Zdroje: