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.
Operaci OperaceX() vytvoříme jako přepsatelnou metodu. Dědic má možnost tuto operaci přepsat, aniž by tuto změnu klient pocítil. Klientovi se "podvrhne" kompatibilní objekt ze třídy dědice s jiným chováním přepínače. Toto řešení je sice flexibilní, ale trochu kostrbaté v tom, že pokud se změní nebo přidá pouze jedna hodnota, musí se přepisovat vždy "celá" operace (tj. nelze problém rozdělit na jednotlivé hodnoty switche).

2.
Jak je podrobně popsáno v knize "Design Patterns v OOP" (e-kniha viz zde), lze takovýto flexibilní switch vyřešit pomocí vzoru COMMAND. Pro každou možnou hodnotu přepínače se zavede subtřída - dědic třídy COMMAND s polymorfní operací Execute(). Z jednotlivých COMMANDů se dynamicky vytvoří kolekce objektů s klíčem reprezentujícím hodnotu. Switch lze tako poskládat doslova "na místě" z objektů v run-time. (pozn.: podrobně vysvětleno v knize).

3.
Podobné řešení jako vzor COMMAND nabízí také CHAIN OF RESPONSIBILITY. V tomto případě se nevytváří kolekce z objektů, ale vytvoříme dynamicky poskládaný řetěz. Hodnota přepínače je vstupním parametrem operace HandleRequest(integer a_hodnota). Každý člen řetezu se rozhoduje podle následujícího schématu: "Je vstupní hodnota moje hodnota? ...ne? ...tak to hodím následníkovi!". Pokud je hodnota "jeho", potom se provede odpovídající větev přepínače. Namísto výběru objektu z kolekce se projde při rozhodování objekty v sekvenci až do zpracování. Důležité je, že všichni členové řetězu podporují stejný interface a lze tedy řetěz libovolně poskládat.

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.


V případě jakýchkoliv připomínek napište prosím na adresu objects@objects.cz