16.10.2016

foto Petr Bravenec

Petr Bravenec
Twitter: @BravenecPetr
+420 777 566 384
petr.bravenec@hobrasoft.cz

Údajně jsou jednou z méně využívaných vlastností jazyka C++ šablony. 73% programátorů používajících jazyk C++ šablony aktivně nevyužívá (nezapočítávají se do toho hotové knihovny jako je std či qt). Údaje vycházejí z průzkumu tiskové agentury JPP (Jedna Paní Povídala). Tím nechci poukázat na neschopnost téměř tří čtvrtin každého programátora v jazyce C++, chci tím jen omluvit svou vlastní neznalost - šablonami v C++ jsem byl dosud dotčen jen okrajově.

Při studiu šablon mě napadlo, že šablony se dají elegantně využít na zjednodušení návrhového vzoru Factory. U tohoto návrhového vzoru bývá několik různých tříd odvozených od společného abstraktního předka, který dokáže podle zadaného parametru vytvořit konkrétní odvozenou implementaci, například:

class X { static X *create(const QString& id); ...
class A, public X { ...
class B, public X { ...
class C, public X { ...

A *a = dynamic_cast<A *>(X::create("A"));
B *b = dynamic_cast<B *>(X::create("B"));

Potíž s klasickým provedením je viditelná na první pohled - metoda X::create() vrací ukazatel na abstraktní třídu a pokud je potřeba přistupovat k odvozené třídě, je potřebné přetypování. Musím ovšem přiznat, že v praxi obvykle nebývá potřeba k odvozené třídě přistupovat. V části programu, kde volám metodu X::create() mě prakticky nikdy konkrétní typ vytvořené třídy nezajímá - abstrakci jsem v aplikaci zvolil právě proto, abych se o typ vytvořené třídy zajímat nemusel či přímo nesměl.

Metoda X::create() může obvykle vypadat třeba takto:

X *X::create(const QString& id) {
    if (id == "A") return new A();
    if (id == "B") return new B();
    return NULL;
}

Pokud je odvozených tříd větší množství, bývá metoda X::create() poměrně rozsáhlá. Další potíž pak nastává při práci s takovým kódem - při zavedení nové odvozené třídy často zapomenu doplnit příslušný řádek a stojí mě to pár dalších minut při zjišťování, proč se moje požadovaná instance třídy nevytvoří.

Vše lze s jistou elegancí řešit pomocí šablon. Celá metoda X::create() vypadá takto:

template<typename T> 
static T *create() {
    return new T();
    }

a vytváření odvozené třídy vypadá takto:

A *a = X::create<A>();
B *b = X::create<B>();

Tímto řešením lze zabít hned tři mouchy jednou ranou:

  • Není nutné přetypování pro získání odvozené třídy, metoda X::create() vrací přímo požadovaný typ.
  • Není nutné doplňovat jednotlivé vytváření jednotlivých typů do metody X::create(). Metoda je kompletní a její zápis vyhovuje pro vytváření jakékoliv odvozené třídy, ať už již existující, či takové, kterou vytvoříte při psaní aplikace později.
  • Pokud se spletete v názvu požadované třídy, dozvíte se o chybě hned při překladu a ušetříte spoustu času s laděním aplikace.

Čistě z pohledu programovacího jazyka je návrhový vzor Factory vytvořený pomocí šablony jednodušší, přehlednější a méně náchylný k chybám.

I s takto zjednodušeným vzorem lze ale narazit: ve svých programech například často získávám seznam tříd k vytvoření z databáze a id třídy předávám metodě X::create(). V takovém případě je stejně nutné vytvořit přepínač, podle kterého se volají konstruktory podle hodnoty id. Snaha o zachování požadovaného návratového typu z metody X::create() pak může vést k přesunutí přepínače mimo metodu. Tím by došlo k naprostému popření významu návrhového vzoru Factory a k vytvoření potenciálně nebezpečné a špatně udržovatelné konstrukce (související funkce se nalézají na několika nesouvisejících místech).

Pokud tedy narazíte na nutnost vytvářet konkrétní instanci odvozené třídy podle id, může být vhodnější použít klasickou implementaci návrhového vzoru Factory.

Hobrasoft s.r.o. | Kontakt