16.10.2016
Ú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:
X::create()
vrací
přímo požadovaný typ.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.Č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.