Lekce 3 - Servant (Služebník)
V minulé lekci, Návrhové vzory GRASP - Dokončení, jsme se zabývali dalšími vzory GRASP přiřazení odpovědnosti. Byly to například Creator, High cohesion, Indirection a další.
V dnešním tutoriálu se budeme zabývat návrhovým vzorem Servant pro přiřazení odpovědnosti. Vzor použijeme tehdy, chceme-li několika třídám přidat další funkčnost, aniž bychom zasahovali do jejich rozhraní.
Motivace použití vzoru Servant
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. Porušili bychom tím princip Don't Repeat Yourself, o kterém se dočteme v lekci Best practices pro vývoj softwaru - Základní praktiky.
Další možností je definovat společného předka, ale to také nelze udělat vždy. 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, čímž bychom porušili princip Single Responsibility Principle, o kterém se dočteme v lekci Best practices pro vývoj softwaru - Základní praktiky.
Pak máme možnost funkčnost vyčlenit do samostatné třídy, takzvaného Služebníka. Jedná se o idiom. Není tedy zařazen mezi „ty pravé“ návrhové vzory GoF. Jeho použití je ale někdy velmi užitečné.
Definice vzoru
Služebník je vlastně takovou přepravkou 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, takzvaných obsluhovaných tříd. 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 instanci obsluhované třídy. Služebník pak bude s obsluhovanými třídami komunikovat přes nějaké rozhraní.
Tímto rozhraním může být například IMovable
,
které poskytne metody setPoint()
a getPoint()
,
abychom mohli objekt posouvat. Toto rozhraní musíme pochopitelně také
implementovat všem třídám, se kterými bude služebník pracovat.
Implementace vzoru
Vzor Služebník můžeme implementovat dvěma způsoby.
První způsob
V tomto případě se dotazujeme na určitou úlohu přímo obsluhované třídy přes služebníka. Služebníku musíme předat instanci obsluhované třídy. Obsluhovaná třída nemusí o služebníkovi vědět.
UML diagram
V UML diagramu vidíme, že obsluhovaná třída o služebníkovi nic neví:

Druhý způsob
V tomto případě si zavolá metodu služebníka sama obsluhovaná metoda. Uživatel tedy nemusí o služebníkovi nic vědět.
UML diagram
V UML diagramu vidíme, že uživatel o služebníkovi nic neví:

Shrnutí obou způsobů
Jak můžeme vyčíst z diagramů, každá třída, která chce být
obsloužena, implementuje rozhraní IObsluhovatelny
. Služebník
pak pracuje 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í, respektive nějakým grafickým objektem, který představuje.
Oba způsoby se liší ve vazbě uživatele na služebníka. Zatímco u prvního způsobu má uživatel ke služebníkovi přímou vazbu, v druhém je volán až obsluhovanými třídami. 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ích tříd. Je realizován samostatnou třídou, která manipuluje s obsluhovanou instancí, respektive s jejím rozhraním).
V další lekci, Object pool (fond objektů), si ukážeme návrhový vzor Object pool, který zamezí opakovanému vytváření objektů, jejichž konstrukce je příliš složitá nebo trvá dlouhou dobu.