\


 Sunday, 21 August 2005
Bázová třída pro business objekty - návrhový vzor Layer Supertype

Na konci spotu, v němž byla vytvořena třída DataCacheHelper, jsem slíbil, že ukážu, jak si vynutit použití objektu DataCacheHelper všemi třídami v business vrstvě. K tomu nám poslouží vzor Layer SuperType, jehož definice se dá parafrázovat takto: zaveďme společného předka pro všechny třídy jedné vrstvy aplikace, v němž zapoudříme společný kód a sdílené chování.

Nyní si popíšeme rozhraní a implementaci vzorového předka pro business objekty. Zobrazit kód. (C#)

Abstraktní třída BusinessObjectBase obsahuje dva konstruktory. Bezparameterický konstruktor je potomky vyvoláván při zakládání nové instance bez obrazu v databázi ("bez platného - perzistentního id). V tomto konstruktoru nastavíme, že objekt není změněn vůči datům v databázi (m_isDirty = false), přidělíme mu dočasné id uložené v konstantě NO_ID a voláním privátních metod setNew a setLoaded označíme objekt jako "nový - bez obrazu v databázi" a současně "není třeba nahrávat data z databáze". U každého nového objektu se také předpokládá, že jde o aktivní objekt - tedy o objekt, který nemá příznak smazán (Discarded) ani neaktivní (InActive) - viz enumerace ObjectState na konci výpisu.

Druhý konstruktor, který přijímá argument id, používají odvozené objekty, jestliže již byly dříve uloženy do databáze. Opět nastavíme příznak, že objekt není změněn vůči datům v databázi (m_isDirty), dále nastavíme proměnnou m_isLoaded na false, což je příznak, který signalizuje, že data objektu ještě nebyl nahrána z databáze, a uložíme si id objektu.

Veřejná vlastnost IsDirty vrátí true, jestliže byl objekt změněn vůči datům v databázi, vlastnost IsNew vrátí true, jestliže se jedná o nový objekt bez perzistence - čeká na "Insert".

Vlastnost IsDeleted má hodnotu true, když již byla data objektu v databázi smazána příkazem Delete (objekt jen "dožívá" v business vrstvě) nebo když u objektů, jejichž data se například kvůli zachování referenční integrity fyzicky nemažou, byl do databáze zapsán stav Discarded.

Vlastnost Id vrací unikátní identifikátor objektu, jestliže byl objekt uložen do databáze, jinak vrací konstantu NO_VALUE.

Vlastnost ObjectState vrací stav objektu s využitím hodnot enumerace ObjectState (aktivní objekt, dočasně neaktivní objekt, smazaný objekt).

Metoda Load je pro nás zajímavá hlavně tím, že si vynutíme použití třídy DataCacheHelper. Jedná se metodu se šablonou (vzor Template method), která nejprve zkontroluje, jestli jde o objekt, který již byl nahrán a/nebo který byl již smazán - pokud je jedna z těchto podmínek pravdivá, metoda Load ihned ukončí svoji činnost. Když jsou všechny předběžné podmínky pro vyvolání metody Load splněny, metoda se pokusí vyzvednout dříve uložený řádek s daty pro svou instanci z třídy DataCacheHelper. Jestliže řádek existuje, pak je vyvolána varianta metody DoInternaLoad přijímající odkaz na řádek, jinak je vyvolána metoda DoInternalLoad, která musí řádek z databáze teprve získat. Metody DoInternalLoad jsou chráněné a virtuální a za jejich implementaci odpovídají odvozené třídy - třída BusinessObjectBase dbá na dodržení základní kostry algoritmu, ale implementaci jednotlivých kroků aloritmu ponechává (musí ponechat) na podtřídách, což je hlavní idea vzoru Template Method. Po úspěšném nahrání objektu je volána metoda setLoaded, která označí objekt jako nahraný (IsLoaded bude nyní vracet true).

Poznámka: V metodě Load se zatím předpokládá, že data objektu potřebujeme nahrát pouze jednou a metoda tedy neumožňuje aktualizaci dat objektu z databáze po prvním nahrání. Jak aktualizaci dat z databáze odvozeným třídám umožníme, si ukážeme v dalším pokračování.

Metoda Save neumožní uložení objektu, jestliže jde o nenahraný objekt ("není co ukládat, data nemohla být změněna"), jestliže jde o vymazaný objekt, nebo pokud objekt nebyl změněn a současně nejde o nový objekt. Metoda tedy již na úrovni předka ošetří, že nebudou zbytečně ukládány objekty, jejichž data byla nahrána, ale uživatel je nijak nezměnil. Metoda pouze volá chráněnou virtuální metodu DoInternalSave a jak asi ti chytřejší z vás tuší, jedná se o další příklad vzoru Template Method. Voláním privátní metody setClear po úspěšném uložení objektu nastavíme na false příznak "změněn" (IsDirty) a "nový objekt" (IsNew). Jestliže byl ukládán objekt, u nějž bylo vyžádáno smazání (ObjectState=ObjectState.Discarded), je nastaven příznak IsDeleted voláním chráněné metody SetDeleted na true.

Metoda Discard slouží ke zrušení objektu, přesněji řečeno k převedení objektu do stavu ObjectState.Discarded - pokud je objekt v tomto stavu a dojde k uložení objektu, metoda DoInternalSave buď záznam v databázi fyzicky vymaže (DELETE) nebo do databáze uloží stav ObjectState.Discarded (UPDATE). Metoda Discard pouze zkontroluje, zda nepracujeme nad smazaným objektem (IsDeleted=true), u nějž opakované rušení nemá smysl, a jestliže tomu tak není, nastaví objektu stav ObjectState.Discarded a označí objekt voláním metody SetDirty jako změněný.

Chráněnou statickou metodu SaveCollection používají odvozené třídy pro ukládání objektů ve svých kolekcích. (vztah agregace nebo kompozice). Výhodou je, že v odvozených třídách nemusí být stále stejný a otravný cyklus foreach, který se liší jen typem iterovaného objektu.

Metody DoInternalLoad a DoInternalSave jsme již zmiňovaly  - jsou to "sloty" používané odvozenými třídami k zavedení vlastní aplikační logiky do schématu vysokoúrovňového scénáře. Bázovou implementaci metody DoInternalLoad přijímající řádek s daty objektu mohou zavolat odvozené třídy, jestliže chtějí nahrát stav objektu (ObjectState) bez zbytečné duplikace kódu - obnovení a ukládání hodnot vlastních specifických vlastností je samozřejmě plně v režii odvozených tříd. Metoda DoInternalLoad bez argumentů ani metoda DoInternalSave nejsou abstraktní, i když by abstraktní být mohly - v business vrstvě ale občas máme třídy, u nichž nemusíme (nechceme) psát perzistenci a proto mají metody v bázové třídě prázdné tělo, abychom nebyli nuceni zcela zbytečně ve více odvozených třídách poskytovat symbolické "prázdné implementace".

Metodu Init mohou odvozené třídy použít pro vlastní inicializační kód (vytváření kolekcí a přihlašování jejich událostí atd.) - příklad uvidíte v pokračování spotu.

Metoda SetDirty nastavuje příznak "změněn oproti datům v databázi" - příznak nemá význam pro objekty pro objekty bez perzistence a pro smazané objekty.

Metoda CheckEquals porovná předané objekty - jestliže se objekty nerovnají, je objekt označen jako změněný. Jak asi tušíte, metodu volají odvozené třídy v set sekci svých vlastností (CheckEquals(oldValue, newValue)) a jsou tak zbaveni nutnosti reagovat na výsledek porovnání a ukládat si vždy každá sama za sebe příznak "jsem změněn". Když uživatel zmáčkne tlačítko uložit na formuláři a my data z formuláře přeneseme do objektu, metoda CheckEquals zajistí, že objekt bude označen jako změněný jen tehdy, pokud uživatel opravdu nějaké změny na formuláři provedl (třeba vybral jinou hodnotu v listboxu). Výhody jsou zřejmé, kód na formuláři je stále týž - pouze "tupě" přenáší data ze vstupních prvků formuláře do vlastností business objektu a ten si již potutelně a sám na úrovni předka ošetří, zda nově předané hodnoty vyžadují uložení, nebo ne, což může nastat, když uživatel z pouhého rozmaru klikne na tlačítko uložit a přitom si detail objektu celou dobu jen prohlíží.

Metoda TriggerLoad iniciuje nahrání objektu, jestliže objekt ještě nebyl nahrán, nejde o nový objekt a také nesmí jít o smazaný objekt. Metodu volají odvozené třídy při přístupu k jakékoli vlastnosti nebo metodě objektu (vzory Lazy Load, Ghost).

Význam dalších chráněných a privátních metod byl již myslím dostatečně osvětlen v předchozím textu při výkladu veřejného rozhrani, takže se nebudu opakovat.

Rozhraní IPersistable vyjadřuje minimální nárok, který musejí splňovat všechny objekty ukládající a obnovující svá data z/do perzistentního úložiště - výhodnou výchozí a závaznou osnovu realizace rozhraní poskytuje třída BusinessObjectBase.

Z enumarace ObjectState pro vyjádření základních stavů objektu prozatím nepoužívame stav InActive - význam ostatních stavů by již vám měl být po přečtení spotu dostatečně zřejmý.

Příště si ukážeme vzorovou implementaci třídy, která dědí z BusinessObjectBase.



Sunday, 21 August 2005 21:29:28 (Central Europe Standard Time, UTC+01:00)       
Comments [1]  .NET Framework | Návrhové vzory