Lekce 2 - Servant (Služebník)
V minulé lekci, Návrhové vzory GRASP, jsme si ukázali návrhové vzory GRASP, mezi které patří například Controller, Creator, High cohesion a další.
Návrhový vzor Služebník použijeme tehdy, chceme-li několika třídám přidat další funkčnost, aniž bychom zasahovali do jejich rozhraní. Služebník nám tak přidává novou funkčnost, aniž by musel být v dané třídě. Jedná se o idiom. Není tedy zařazen mezi „ty pravé“ návrhové vzory z GoF. Jeho použití je ale někdy velmi užitečné.
Motivace
Někdy se můžeme dostat do situace, kdy máme určitou skupinu tříd, kterým chceme přidat další funkčnost. Přímou implementací bychom ale tuto funkčnost museli zkopírovat do každé z nich, což je při programování popř. rozšiřování aplikace dálnice do pekel (nehledě na to, že tím porušujeme princip DRY). Další možností je samozřejmě definovat společného předka, ale to také nelze vždy udělat. Funkčnost, o kterou chceme třídy rozšířit, s nimi totiž nemusí vůbec souviset. Třídy by se pak musely starat o víc věcí naráz (princip SRP, který je také velmi důležitý při návrhu). V takovém případě můžeme funkčnost vyčlenit do samostatné třídy (tzv. Služebník).
Vzor
Jedná se vlastně o takovou přepravku na metody, které obsluhují instance
určité skupiny tříd. Služebník je samostatná třída, která
obsahuje metody manipulující s instancemi původních tříd (dál jen jako
obsluhované). Budeme-li chtít po nějaké obsluhované třídě
splnit určitou úlohu (třeba plynulý posun - animaci), požádáme o tuto
úlohu služebníka, kterému předáme právě instanci obsluhované třídy.
Služebník pak bude s obsluhovanými třídami komunikovat přes nějaké
rozhraní (např. IMovable
- v tomto případě poskytne metody
jako setPoint()
a getPoint()
, abychom mohli objekt
postupně posouvat). Toto rozhraní musíme pochopitelně také implementovat
všem třídám, se kterými bude služebník pracovat.
V podstatě můžeme vzor Služebník implementovat dvěma způsoby: obsluhovaná třída nemusí o služebníkovi vědět vůbec nic (ukázka - UML diagram 1), nebo naopak o služebníkovi nemusí nic vědět uživatel (ukázka - UML diagram 2). V prvním případě se dotazujeme na určitou úlohu přímo obsluhované třídy přes služebníka, kterému musíme předat instanci obsluhované třídy. V tom druhém o nějakém služebníku nemusíme nic vědět. Po dotázání na určitou úlohu obsluhované třídy si zavolá metodu služebníka sama obsluhovaná metoda. Obě tyto implementace jsou správné. Záleží na vás, jak budete chtít služebníka implementovat.
.<> Operace požaduje uživatel od samotného služebníka
.<> Operace požaduje uživatel od obsluhované třídy
Jak můžeme vyčíst z diagramu, každá třída, která chce být
obsloužena, implementuje rozhraní IObsluhovatelný
. Služebník
pak pracuje pouze s tímto rozhraním. Pokud bychom chtěli služebníka pro
animaci, budeme nejspíš potřebovat již zmíněné metody
getPoint()
a setPoint(Point)
, díky kterým bychom
mohli posouvat obsluhovanou instancí (resp. nějaký graficky objekt, který
představuje). Příklady se liší ve vazbě uživatele na služebníka.
Zatímco v prvním případě má uživatel ke služebníkovi přímou vazbu, v
druhém je volán až obsluhovanými třídami a uživatel na něj tedy nemá
přímou vazbu, čehož lze využít třeba pro zapouzdření.
Závěr
Vzor Služebník použijeme tehdy, chceme-li přidat skupině tříd nějakou schopnost a nechceme ji přitom implementovat do původní třídy. Je realizován samostatnou třídou, která manipuluje s obsluhovanou instancí (resp. s jejím rozhraním). Jedná se o idiom a není tedy v GoF.
V další lekci, Object pool (fond objektů), si ukážeme návrhový vzor Object pool, který zamezí opakovanému a zbytečnému vytváření objektů, jejichž konstrukce je příliš složitá nebo trvá dlouhou dobu.