ObrázokAk riešite problém konštrukcie tried v objektovo orientovanom jazyku, určite je dobre poznať návrhového vzory (design patterns). Sú to hotové riešenia pre určité skupiny problémov. Samo o sebe to znie fantasticky. Nájdem vzor a použijem. Niečo ako instantná polievka, ktorú stačí len zaliať horúcou vodou a hotovo. Problém je, že keď si prenesiete myšlienku toho vzoru do svojho kódu, môžete rýchlo naraziť na komplikácie. To, čo sa zdalo v knihe také jednoduché a jasné, sa zrazu začne zamotávať. Niečo sa nedá urobiť tak, ako navrhuje kniha, inak by sa objavila komplikácia niekde inde, v inej časti by došlo použitím vzoru k spomaleniu aplikácie atď. Medzi svetom živého kódu a tým, v ktorom žijú návrhového vzory, je rozdiel. A na neho sa teraz pozrieme.

Hneď na začiatku musím povedať, že toto nie je kritika návrhových vzorov. Vzory sú veľmi užitočný nástroj ako sa vysporiadať s niektorými problémami najlepším možným spôsobom. Viem to, lebo som ich za históriu, čo sa živím programovaním, použil už veľakrát. Tento článok je skôr zamyslenie sa nad tým, prečo je ich použitie občas ťažšie ako by sa na prvý pohľad mohlo zdať.

Štandardne sú vzory, či už v knihe ale na internete, prezentované v jedno-problémovom svete. To je svet, kde existuje len jeden problém. Ak sa napríklad bavíme o vzore Facade, tak ten je zobrazený vo svete, kde hlavným problémom je zjednodušenie rozhrania. Znamená to, že triedy, ktoré sa v príklade o Facade vyskytujú, majú len tento jeden nedostatok (ich použitie môže byť komplikované a Facade ho zjednodušuje). To, že je Facade v takomto svete prezentované, je v poriadku. Cieľom je komunikovať myšlienku, na čo je Facade užitočný (čo rieši), a teda musí byť jasný problém, aby som pochopil, či je podobný tomu môjmu. Zatiaľ to všetko sedí. Jeden problém, jedno riešenie.

Komplikácia pri použití vzorov nastáva v tom, že žiadna reálna produkčná aplikácia nemá len jeden problém. Požiadavky funkčné aj nefunkčné vytvárajú množstvo problémov, ktoré treba riešiť. A občas sú riešenia protichodné alebo ich kombináciou vzniká komplikovaný kód. Pekným príkladom protichodných požiadaviek a ich riešení je vysoká rýchlosť kódu a flexibilita aplikácie pri zmene funkčnosti. Zatiaľ čo to prvé vo väčšine prípadov vyžaduje pevné väzby medzi modulmi alebo triedami, to druhé naopak väzby čo najslabšie. Takto sa musí vždy hľadať kompromis na základe toho, ktorá požiadavka je ako dôležitá.

Podobne je to s návrhovými vzormi. Tie nemusia byť protichodné, ale veľmi často sa kombinujú, resp. prichádzajú do stretu s inými nefunkčnými požiadavkami, ktoré som spomínal v predchádzajúcom odstavci. Príkladom takej komplikácie je použitie MVC vzoru. Je to vzor, ktorý núti rozdeliť triedy aplikácie do troch kategórií podľa účelu danej triedy. Na prvý pohľad vyzerá tak jednoducho a univerzálne, že by sa mohlo zdať, že jeho použitie bude jednoduché. Ale počnúc behovým prostredím, cez vývojové prostriedky a končiac samotným návrhom vám môžu vzniknúť triedy, ktoré budete mať problém zaradiť do niektorej z týchto kategórií. Osobitnou kapitolou v prípade MVC je rozhodovanie, či má View vrstva mať priamy kontakt na Model vrstvu. Ak sa vývoj nemá príliš komplikovať, tak áno. Ak chcem zachovať čistotu návrhového vzoru, tak by ju mať nemala a všetky údaje by mala získavať cez vrstvu controlera. Implementácií a variácií MVC je mnoho a toto je len ukážka z rozhodovacieho procesu.

Tu som sa dostal k asi najväčšiemu problému, s ktorým vzory zápasia. Ich použitie totiž vyžaduje dodržiavanie určitých pravidiel pri písaní kódu. Znamená to, že občas treba použiť dlhšiu cestu, aj keď existuje skratka. Vytvoriť rozhranie k triede alebo rozdeliť pripravovanú funkcionalitu do viacerých tried namiesto jednej. A zatiaľ čo v čarokrásnom svete návrhových vzorov je času dostatok na všetko, v tom reálnom sú čas peniaze. Časové hľadisko môže úplne zastaviť alebo výrazne obmedziť implementáciu vzoru. A teda keď to v knihe vyzerá tak jednoducho a super, v praxi zistíte, že by ste museli vytvárať rozhranie pre všetkých týchto n-tried a začne sa to komplikovať.

Správne implementovaný vzor v nejakej časti aplikácie je niečo, čo dokáže ešte dlho po začatí vývoja udržať návrh v čistom stave. Každý vzor má totiž pre všetky svoje triedy jasne definované zodpovednosti. Pre kohokoľvek, kto pozná teóriu daného vzoru, je potom tento kód omnoho ľahšie stráviteľný. Umožňuje baviť sa o kóde na vyššej úrovni abstrakcie atď. Toto všetko a ešte viac sú ciele, ktoré sú na konci cesty implementácie návrhových vzorov do aplikácie. Po tej ceste sa ale nemusí ísť vždy ľahko. Použitie vzorov komplikujú iné požiadavky na kód, ich kombinovanie v rámci jednej aplikácie alebo jednoducho nedostatok času ich plne implementovať. Je dobré mať tieto riziká pri ich používaní na pamäti.