Vzor OBSERVER a jeho zajímavá varianta v kombinaci se vzorem ADAPTER Část 2
autor RNDr. Ilja Kraval, http://www.objects.cz únor 2007 firma Object Consulting s.r.o.
© Ilja Kraval, 2007, http://www.objects.cz
Úvod V předešlé části článku jsme si uvedli nejjednodušší variantu použití vzoru OBSERVER. Nyní se vrátíme k problému s našimi auty a barvami a uvedeme si, jak by se dal tento vzor pro řešení našeho příkladu nasadit. Hned v úvodu si však musíme uvést jednu důležitou okolnost. Vzor OBSERVER předpokládá, že pokud při jeho použití hovoříme o objektech, tak máme na mysli rovnou a přímo objekty dané technologie objektového prostředí, tj. hovoříme zde o objektech v prostředí .NET nebo JAVA atd. Při použití tohoto vzoru tedy nehovoříme o tzv. logických instancích v analytickém modelu, tj. o analytických evidovaných instancích, ale hovoříme o opravdu „žijících objektech v OOP“. Jinak řečeno vzor OBSERVER je nasazen přímo v objektovém prostředí na „opravdové žijící objekty“ vytvořené voláním konstruktoru v tomto prostředí. A právě zde musíme zvážit jeden „drobný problém“. Pokud bychom v přechodu od analytického modelování do designu mapovali logické instance (tj. evidované instance) do OOP přímo jedna k jedné a platilo by proto „kolik evidovaných instancí, tolik objektů v paměti v OOP“, potom by nasazení tohoto vzoru bylo opravdu jednoduché až primitivní. Každý objekt auta (ve smyslu OOP), který se provazuje přes ukazatel na svou barvu, by se spolu s tvorbou této objektové vazby současně také zaregistroval k danému objektu barvy, například nějak takto (o principu registrace viz předešlá část článku):
class CAuto : IObserver; //...JAVA implements IObserver { private CBarva mBarva; public SetmBarva (Cbarva aBarva); //nastavení barvy do auta { mBarva = aBarva; mBarva.GetOnChangeBarva.Add(this); //registrace k události v barvě } ... }
strana 2
© Ilja Kraval, 2007, http://www.objects.cz
Takto nejenom že objekt auta „vidí“ svůj objekt barvy, ale současně je dané auto také zaregistrováno k požadované události v barvě a proto bude přes interface IObserver auto zavoláno (až nastane událost v barvě) odpovídající metodou Update(). Asi tušíme, v čem je problém tohoto příkladu. Pro tzv. „enterprise systémy“, tj. systémy evidenční (neboli podnikové) je příznačné, že obsahují na analytické úrovni opravdu „hodně multiinstanční“ třídy s vysokým počtem logických instancí. Jinak řečeno pro podnikové systémy je charakteristické, že počet evidovaných výskytů multiinstančních entit může být velmi velký. Pro náš příklad to konkrétně znamená, že analyticky řečeno v systému se může vyskytovat řádově tisíce až desetitisíce evidovaných aut. Pokud bychom tedy tyto logické instance aut mapovali do objektového prostředí jedna ku jedné, nastane evidentní technologický problém se správou takového množství žijících OOP objektů. Závěr je jasný: Nemůžeme z tohoto důvodu použít jednoduchou variantu nasazení vzoru OBSERVER, která byla uvedena v předešlém odstavci, tj. tu variantu nasazení vzoru OBSERVER, kdy se auto současně s vazbou na barvu také zaregistrovalo a poté je zavoláno. Nemůžeme prostě všechny objekty aut a všechny objekty barev držet v paměti a čekat na události v objektech - barvách, které potom „volají zpět“ své zaregistrované objekty - auta. Musíme tedy provést jiné mapování a vzor OBSERVER nasadit na jiné instance objektů. Otázkou je, jak to udělat?
Mapování analýzy podnikových systémů do designu, vzor FLYWEIGHT a jeho různé podoby v dalších vzorech V podstatě řešíme klasický problém, kdy máme část analytického modelu u podnikového systému již hotovu (zde auta a barvy) a snažíme se ji mapovat do technologie hybridního systému, tj. do technologie „silné prostředí OOP plus silná databáze“ (např. C# plus MS SQL nebo JAVA plus ORACLE apod.) a to jinak, než pro velký počet instancí technologicky nesmyslným stylem „počet instancí v analýze a designu bude jedna ku jedné“ . Připomeňme, že situaci, kdy máme hodně logických instancí na straně jedné a relativně k tomu málo instancí v prostředí OOP, řeší obecně vzor FLYWEIGHT v jeho různých modifikacích. Pro podnikové systémy (neboli tzv. enterprise systémy) řešené pomocí hybridního systému se tato sada takovýchto řešení nazývá speciálně „Patterns of Enterprise Architecture Systems“, zkráceně „Enterprise Architecture Patterns“ (blíže o této literatuře viz článek „Známé vzory při návrhu informačních systémů“ na našem serveru). strana 3
© Ilja Kraval, 2007, http://www.objects.cz
Tyto vzory udávají známé postupy při řešení těchto situací: Známe logické řešení (analytický model tříd), máme hybridní systém, tj. silný jazyk OOP a silnou databázi, a otázkou je, jaké tedy potom zvolíme technologické řešení našeho logického problému? Uveďme například, že ve zmíněné knize existuje celkem 51 vzorů, které se týkají této problematiky mapování do hybridních systémů. Pro náš příklad zvolíme lehce upravený vzor DOMAIN MODEL v kombinaci se vzorem MAPPER. Stručně řečeno by naše řešení bylo zavedeno takto: Nalezenou multiinstanční analytickou třídu mapujeme na jednu tabulku a jednu třídu s odpovídajícími názvy podle analytické třídy (například z analytické třídy auto vznikne jedna tabulka TAuto v databázi a jedna třída CAuto v OOP), dále zavedeme nějaký pomocný datový objekt, který zprostředkovává přístup k databázi, nazvěme jej třebas DBO, za kterým je schován objekt připojení do databáze. Metody tohoto datového objektu odpovídají funkcionalitám, které potřebují objekty z OOP při spolupráci s databází (například „vlož data nového auta“ apod.). Tento datový objekt se svým interfacem se chová velmi podobně jako okénko úschovny zavazadel, objekty z business vrstvy si přes něj „odkládají svá data“ a zase podle stvrzenek (tj. idéček) opět vybírají. Za okénkem úschovny zavazadel jsou nějak uspořádány regály pro data (neboli tabulky ☺). Funkcionality itemů se podle tohoto vzoru (např. co umí „jedno auto“) mapují jako dynamické členy tříd a funkcionality seznamu (např. „najdi v seznamu aut právě ta auta, která...“) se mapují na statické metody této třídy. Je zřejmé, že v určitém okamžiku delegují tyto funkcionality svůj běh na datový objekt DBO. I když máme vše pěkně „pohromadě“ v jedné třídě, tak toto mapování není až tak výhodné a v praxi jsme v projektech se statickými metodami „narazili“. Nevýhody zavedení statických metod pro funkcionality seznamu jsou hned dvě: Za prvé, statické metody nemůžeme přepisovat (a opravdu i chování seznamu se může chovat polymorfně) a za druhé, je možné (a dokonce to nastane v našem případě při zavádění vzoru OBSERVER!), že budeme chtít se seznamem „handlovat“ jako s objektem, což nám třída se statickými metodami neumožňuje. Zvolíme proto lehkou modifikaci tohoto vzoru a totiž, že analytickou třídu budeme do OOP mapovat vždy nikoliv na jednu třídu, ale NA DVĚ TŘÍDY. První třída bude třídou pro item (to zůstává stejné jako před modifikací) a druhá třída bude správcem seznamu a v ní budou metody odpovídající funkcionalitám seznamu těchto itemů. Jinak řečeno, statické metody v nemodifikované variantě se převedou do jiné třídy, do správce seznamu, a stanou se tak dynamickými metodami. Dále učiňme názvovou dohodu, že třídy spravující seznamy vybavíme předponou col (jako „collection“). Navíc, pokud nejsou prvky seznamu v kompozici vůči svému majiteli (jako jsou například řádky faktury ve faktuře apod.), tak v tom případě prvky těchto tříd vlastní sám systém a nikoliv nějaký prvek. Zavedeme proto tyto třídy správců seznamu jako jedno-instanční podle vzoru SINGLETON.
strana 4
© Ilja Kraval, 2007, http://www.objects.cz
Vyplývá z toho, že pro náš příklad máme tedy hned 4 třídy v OOP (pomineme pro nás nezajímavý služební datový objekt DBO obalující konekt do DB a poskytující okénko do úschovny dat) vzniklých ze dvou entit, zde auto a barva: CAuto, CcolAuto, CBarva a CcolBarva a vzniknou také dva objekty podle vzoru SINGLETON a to gcolAuto a gcolBarva jako dva globální správci seznamů ze tříd CcolAuto a CcolBarva . Jedna velmi důležitá poznámka (!): Objekty gcolAuto a gcolBarva mají sice metody pro funkcionality seznamů (speciálně pro auta a pro barvy), ale to vůbec neznamená, že za nimi opravdu stojí v paměti (v OOP) skutečně žijící objekty. Jak jsou tyto metody uvnitř napsány, to záleží na technologovi, on rozhodne, co se udělá „ v paměti v objektech“ a co v databázi. Například metoda „dej počet prvků seznamu aut“ getCount se může pouze ztransformovat na dotaz do DB, například v pseudokódu nějak takto:
class CcolAuto { public long GetCount(); { return DBO.GetCountforAuto(); } ... }
a uvnitř metody objektu DBO.GetCountforAuto() se volá přes konekt jednoduchý SQL příkaz, například „SELECT COUNT(*) FROM TAuto“. Nyní je otázkou, jak při tomto mapování zavedeme odpovídající vzor OBSERVER. Je zřejmé, že nemůžeme použít již zmíněnou primitivní variantu, kdy objekt barva „obtelefonuje“ své zaregistrované objekty aut posazených jako objekty OOP v paměti, protože jak vidět, uvnitř objektu gcolAuto se nemusí vyskytovat žádný žijící objekt auta ve smyslu OOP (viz metoda GetCount v příkladu ☺) a není proto koho obtelefonovat. Kdo koho tedy bude ve vzoru OBSERVER zpětně volat a jak?
strana 5
© Ilja Kraval, 2007, http://www.objects.cz
Zavedení vzoru OBSERVER v našem příkladu Je zřejmé, že musíme problém registrace převést na úroveň správců seznamů. Tam se totiž nabízí jednoduché řešení. Nechť máme v objektu barva zavedenu nějakou změnu, na kterou čekají ta auta, která mají tuto barvu. Při změně jednoho objektu barva (máme jej dočasně například pro editaci vytvořen) nebude tento objekt obtelefonovávat „svá auta“ (to teď nelze, kdoví zda jsou v té chvíli auta vůbec v paměti!), ale zavolá svůj objekt správce seznamu barev. Tento správce barev zavolá všechny své zaregistrované objekty jiné správce seznamů - přes interface IObserver a mezi nimi bude také zaregistrován seznam aut. Seznam aut se už postará o to, aby auta s danou barvou zareagovala, buď v databázi, anebo objektově v paměti. V této konstrukci však musí dojít k jedné drobné změně: Vstupním parametrem těchto volání musí být vždy daný objekt barvy anebo stačí pouze jeho idBarva, aby seznamům bylo jasné, kdo tuto událost vyvolal a podle toho se provede reakce u těch aut, která toto idBarva mají jako cizí klíč. Pseudokód barvy může vypadat nějak takto:
class CBarva { private long idBarva; ... ...//něco se děje v nějaké metodě a ostatní mají reagovat gcolBarva.OnChangeBarva.UpdateAll(idBarva) //volám mého správce barev! ...//pokračujeme dále ... }
Všimněte si, že objekt barvy vůbec nevolá své zaregistrované objekty, ale zavolá svému správci, aby on zavolal své zaregistrované objekty! V metodě UpdateAll(idBarva) u správce barev se zavolají všechny registrované objekty ke správci barev, mezi nimi bude podle našeho příkladu i objekt správce aut
strana 6
© Ilja Kraval, 2007, http://www.objects.cz
gcolAuta. U něj se spustí metoda Update(idBarva) a v této implementované metodě seznamu se s pomocí tohoto vstupního parametru určí, která auta ze seznamu mají reagovat. Například je možné, že objekt gcolAuta provede jako reakci opět pouze jako transformaci do DB a pouze zavolá DBO objekt nějakou metodou a v ní bude spuštěna nějaká stored procedura – vstupním parametrem bude samozřejmě převzaté id, zde idBarva. Je zřejmé, že objekt barvy nemusí předávat jako vstupní parametr idBarva, ale může namísto toho předat sám sebe (kdo potřebuje id, tak si jej vyžádá přes property). Pak by se kód v řádku volání správce barev změnil takto:
...//něco se děje v nějaké metodě a ostatní mají reagovat gcolBarva.OnChangeBarva.UpdateAll(this) //volám mého správce barev! ...//pokračujeme dále
Výhodou předání celého objektu barvy v předešlé konstrukci je možnost získat z barvy i jiné údaje, než pouze idBarva. (poznámka: samozřejmě tyto údaje včetně idBarva musí být vyvedeny ven z objektu barva nějakými „public metodami“, například přes property). Posloupnost volání kódu je tedy následující: 1. pracujeme s nějakým jedním objektem ze třídy CBarva. 2. dojde k nutnosti vyvolat událost, na kterou čekají auta s danou barvou 3. zavolá se správce seznamu barev, aby danou událost vyvolal na své úrovni. Současně se mu předává buď idBarva anebo celý objekt barvy, u něhož došlo ke změně 4. správce barev zavolá všechny své observery, mezi nimi je registrován i správce aut, zavolá jim všem metodu Update a dá jim vstupní parametr buď idBarva anebo objekt Barva. 5. uvnitř registrovaného správce seznamu aut dojde ke zpracování události se vstupním parametrem barva nebo idBarva. Uvnitř seznamu aut se buď zavolá datový objekt a vyvolá se nějaká odpovídající stored procedura anebo se vyvolají a zkonstruují objekty aut pouze s danou vstupní barvou a jim se zavolají odpovídající metody.
strana 7
© Ilja Kraval, 2007, http://www.objects.cz
Závěr části 2 U podnikových neboli tzv. enterprise systémů nemůžeme většinou použít vzor OBSERVER přímo na objekty z multiinstačních tříd pocházejících z logického modelu, protože logických instancí je většinou příliš mnoho a vzor OBSERVER předpokládá, že „čekající objekty“ budou umístěny v paměti, aby jim bylo možné zavolat jejich metody. Jedním z řešení je provázat observery na úrovni správců seznamu. Znamená to, že čekajícími objekty (observery) se stávají správci seznamů, kteří jsou zaregistrováni k jiným správcům seznamů, jejichž itemy vyvolávají události. Vyvolání observerů probíhá tak, že daný prvek, který vyvolává událost, zavolá svého správce seznamu a ten zavolá všechny k němu zaregistrované správce seznamů. Přitom se samozřejmě musí předat vstupní parametr, který identifikuje ten prvek (z mnoha), který tuto událost vyvolal, například vstupním parametrem je buď id prvku nebo celý prvek jako objekt. U reagujících zaregistrovaných správců seznamů se tímto vyvolá metoda seznamu, která tuto reakci přenese na všechny ty prvky v seznamu, které používají daný vstupní parametr a další reakce u prvků se provede buď v databázi anebo objektově v paměti. ---V příštím článku se dostaneme ke slíbené modifikaci vzoru OBSERVER v kombinaci se vzorem ADAPTER a jaké to přinese výhody. Konec 2. části
strana 8