|
Ilja Kraval : Použití vzorů COMMAND a CHAIN OF RESPONSIBILITY pro flexibilní "switch" a kombinace těchto vzorů se vzorem VISITOR (2.12.2002) Představme si, že máme za úkol vybudovat switch, který v pseudokódu "JAVACIS" může vypadat nějak takto:
public void OperaceX(a_Hodnota);
{
switch a_Hodnota
{
0 : UdelejA();
1 : UdelejB();
2 : UdelejC();
}
}
Pokud se vyžaduje flexibilita kódu vůči přidání dalších hodnot anebo se vyžaduje změna
hodnot, potom můžeme postupovat několika možnými způsoby (pokud znáte další, napište):
1.
2.
3. Uvedené úvahy použijeme pro rozšíření možností vzoru VISITOR. Již v několika firmách byla v diskusích při školení Design Patterns nastolena otázka "jak se zbavit ne-flexibility ve vzoru VISITOR". Jak známo, vzor VISITOR flexibilně přidává virtuální "pseudooperaci" do stromu dědičnosti a to bez zásahu do kódu tříd. Na druhou stranu je však velmi "kožený" vůči přidání nového Elementu ve stromu dědičnosti, kam se pseudooperace přidává. Přidání nového elementu totiž vyžaduje přidat v tomto vzoru novou přepisovanou operaci s názvem "VisitNovyElement" (viz zmíněná e-kniha, obrázek 58). Nabízí se varianta, že by Visitor neměl N operací podle šablony "VisitConcretElementA", ale že by měl pouze jednu operaci VisitElement a uvnitř ní na základě toho, jakého typu je přijatý element, by došlo k rozhodnutí, jak se má provádět výpočet, něco na tento způsob:
public VisitElement(Element a_Element);
{
switch ClassName(a_Element)
{
'CKruh' : ObsahKruh(a_Element);
'CObdelnik' : ObsahObdelnik(a_Element);
...
}
}
Všimněme si, že jsme se opět dostali k problému switche, který lze řešit flexibilně a to buď pomocí COMMAND anebo pomocí CHAIN OF RESPONSIBILITY. Přidání nového elementu (bez flexibility) by jinak znamenalo změnit předešlý kód, otevřít jej a přidat další větev přepínače. Aplikace vzoru CHAIN OF RESPONSIBILTY zavede třídy podle šablony: "ConcreteVisitorforConcreteElement", kde proměnné šablony jsou "ConcreteVisitor" a "ConcreteElement". Třídy mají oproti obrázku 58 e-knihy jedinou polymorfní operaci VisitElement(a_Element). Uvnitř této operace se nejprve rozhoduje, jakého typu je vstupní element a pokud je "souhlasného" typu, potom se zpracuje, pokud není "souhlasného" typu, potom se posílá element následníkovi v řetězi Visitorů. Element tak nevstupuje do operace VisitConcreteElement, jak ukazuje obrázek 58, ale projde Visitory pro různé elementy až se najde ten správný pro daný element a provede se výpočet. Pokud se jedná o "sumační Visitor" (k tomu účelu se používá), potom může oproti předešlé variantě vyvstat problém, kde se má vlastně uchovávat sumační hodnota. V původní variantě totiž všechny operace VisitConcretElement patřily pod jeden objekt Visitora a ten si mohl držet tuto hodnotu (viz obrázek 59). Zde je však N "malých" Visitorů, každý reprezentuje jednu operaci VisitConcreteElement a teprve dohromady jejich řetěz je to, co před tím bylo Visitorem. Řešení můžeme navrhnout buď tak, že si budou všichni členové řetězu ukazovat běžnou asociací na společný objekt, nebo si tuto hodnotu budou visitoři předávat jako druhý vstupní parametr anebo použijeme vzor SINGLETON - umístíme "sumační hodnotu" do globální viditelnosti. Příklad pseudokódu pro visitora DejObsah pro Kruh může vypadat podle následující konstrukce. Jedná se o Visitora, který umí počítat obsah kruhu a přičte tento obsah do sdíleného obsahu (tento vidí všichni členové tohoto řetězce pro výpočet obsahu). Poznámka: Při budování řetězu se vyplnily přes property ukazatele na mNextVisitor a ukazatel (referencí) mSumaObsah (mSumaObsah sdílí referenci na původní číslo, nikoliv přesypáním).
class CVisitorDejObsahforKruh : CVisitor;
{
//members
private CVisitor mNextVisitor;
private real mSumaObsah;
//operations
public override void VisitElement(CElement a_Element);
{
if ClassName(a_Element) <> 'CKruh'
then mNextVisitor.VisitElement(a_Element)
else
{
MyKruh = CKruh(a_Element);
mSumaObsah = mSumaObsah + Mykruh.R * MyKruh.R * constPI)
}
}
Podobně můžeme napsat Visitora pro DejObsah obdélníka, bude se lišit pouze ve dvou bodech: Bude se ptát,
zda se jedná o obdélník a bude mít jiný vzorec pro výpočet obsahu. Z těchto dvou a dalších "minivisitorů"
vytvoříme řetěz v run-time. Tento řetěz reprezentuje jednoho původního konkrétního Visitora pro Obsah
podle obrázku 58, tj. tento řetěz reprezentuje jeden původní objekt ze třídy VisitorDejObsah
zavedeného původně v e-knize. Jinak řečeno, jeden objekt s N operacemi je nahrazen N objekty
se "stejnou" polymorfní operací. Nyní není problém zavést nový Element: Do řetězu se přidá nový objekt.
Druhá podobná možnost spočívá v řešení, kdy nezavedeme řetěz, ale kolekci objektů "vedle sebe". Podle typu elementu se z této kolekce vybere patřičný objekt a provede se operace. Rozdíl je v tom, že v řetězu se každý rozhoduje sám uvnitř sebe (je to moje, není to moje), přičemž se postupně prochází řetěz (není to moje, tak to dám dál). Druhá možnost s kolekcí (vzor COMMAND) předpokládá "někoho", kdo z itemů vybere ten správný podle klíče a zavolá mu unifikovanou operaci.
|