|
Ilja Kraval : Zajímavé konstrukce kombinatorických entit s odkazy do číselníků v UML (25.1.2003) Při jedné konzultaci ve firmě ve Valašském Meziříčí, která dodává evidenčně-ekonomické systémy speciálního typu, vznikla při modelování zajímavá otázka. Při jejím řešení jsem si uvědomil, že by bylo vhodné uvést podobný příklad v článku hned ze dvou důvodů: Jednak se může jednat o obecnější problém týkající se vícero typů ekonomických systémů a také se jedná o pěknou ukázku síly analytického modelování v UML a to i s praktickou ukázkou mapování až do designu, případně až do kódu. V mnoha případech se vyžaduje, aby v systému vznikly entity, které mají tak říkajíc "spoustu odkazů" do "spousty různých číselníků". Přitom vznikají různé kombinace jako jeden svazek s různými odkazy. Jedna entita může mít například 3 položky s třemi odkazy do tří číselníků a jiná entita má například 2 položky se dvěma odkazy do dvou číselníků. Vznikají tak různé "kombinatorické" entity podle toho, o jaké položky s jakými číselníky se v položkách jedná. Přitom se vyžaduje, aby uživatel systému mohl přidat další možnou kombinaci. Jinak řečeno existuje případ užití "založení nové kombinatorické entity". Klasickými příklady jsou systémy typu dílny, autoservisy, apod. (resp. obecněji systémy evidující jakékoliv zpracování). Zde se zavádějí tzv. normativy, tj. jednotky norem zpracování o N různých položkách. Jeden normativ určuje pracnost nějakého úkonu (například je dán cenou, počtem hodin apod.). Jeden normativ je složen z N položek, každá položka má odkaz do "svého" číselníku. Jiným příkladem mohou být různé klasifikace ekonomických subjektů apod. Pro další úvahy je důležité to, že zmíněné číselníky (kam si ukazují položky) jsou různého typu a patří tedy do různých tříd. (pozn.: Jak by se situace zjednodušila, kdyby tomu tak nebylo a existoval by pouze jeden číselník!) První a nepřesný návrh řešení spočívá v zavedení pouze jedné jediné položky, která bude mít všechny odkazy do všech možných číselníků. Pro výskyt dané kombinaci bude tedy platit pouze několik málo odkazů a ostatní nebudou platné, tj. tyto neplatné odkazy nebudou naplněny a budou mít odkaz na nic, tj. mít hodnotu null. V modelu UML se tato situace znázorní například takto (pozn.:diagramy jsou tvořeny v nástroji Enterprise Architect 3.50): obrázek 1 Zde třída CKombinator hraje roli držitele kombinace (například se jedná o zmíněný normativ). Jeho výskyty tvoří různé možné kombinace. Například instance Normativ1 si ukazuje na item z CCis1 a na item z CCis2, ale neukazuje si na žádný z ostatních. Po mapování do designu se toto v relační databázi projeví jako záznam se dvěma relevantními cizími klíči ID (IDCis1 a IDCis2 mají nějakou hodnotu), ostatní ID na další C číselníky mají hodnotu null. Je zřejmé, že toto řešení bude sice funkční, ale není čisté. Tato nečistota se projeví velmi nepříjemně ve dvou ohledech:
Model navrhneme nyní takto: obrázek 2 Všimněte si, že uvedený obrázek vskutku zavádí N položek, které vůči držiteli kombinace vystupují jako "obecné položky" v téže roli (jsou prvky seznamu Položky Kombinátoru). Na druhé straně každá z nich vystupuje samostatně jako svůj typ díky specializaci (zástupnost role zespodu nahoru, která se projeví jako kompatibilita mezi dědicem a předkem ve statických jazycích - JAVA, DOT NET, Object Pascal apod.). Předešlý obrázek ukazuje analytický pohled (tj. konceptuální rovinu). Dobré je, abychom si hned na základě tohoto obrázku dovedli představit tento model v technologii jako "reálně fungující kus programu" a nechápali tento obrázek pouze jako nějakou hypotetickou chiméru. Za tímto obrázkem je totiž skryto na nižší abstraktní úrovni "modelování designu" velmi konkrétní řešení. Otázka tedy zní: Jak si tento model představit přímo v programu? Zvolme tedy jako příklad nějakou technologii, bude se třeba jednat o RDB databázi (např. ORACLE nebo MS SQL apod.) a jazyk bude buď JAVA, DOT NET nebo Object Pascal. První je obraz v DB. Zde je postup jednoduchý (mapujeme 1:1), odpovídající obrázek si lze představit jako obrázek tabulek v ERD diagramu. Vybavme všechny tabulky vlastním systémovým klíčem podle vzoru "ID + název entity". Dostaneme tak podle pravidel EFEM také řešení, jak putují cizí klíče. Zde se uplatní tato pravidla "odkud kam putuje klíč":
obrázek 3 Další krok je mapování v aplikaci. Kromě jiných scénářů budou v aplikaci existovat scénáře pracující přímo s "živými" instancemi, zavedeme tedy mapování do OOP takto:
Buďme v příkladu "perfekcionalisté" :) . V úvahách však lze totiž pokračovat dále. Když se podíváme na obrázek 2, tak vidíme, že specializujeme položky. Ale úvaha by mohla být i taková, že zástupnost rolí bude hrát jak položka, tak číselník. Znamená to, že model by byl analyticky ještě složitější. Vazbu na číselník vyvedeme do úrovně předka položky a "sjednotíme jeho roli" přes společného předka číselníků. Model potom vypadá takto: obrázek 4 Model se tímto poměrně stal poněkud složitější, takže je otázka, jaký by měl tento krok smysl. Jinak řečeno, kdy který model použít? To je otázka jak v rámci analýzy, tak designu. Označme si použitelné modely a proveďme nad nimi diskusi: Model A, obrázek 4: Jedná se o analytický model, který poskytuje flexibilitu v tom smyslu, že můžeme specializovat položky (bez ohledu na číselníky) a specializovat číselníky (bez ohledu na položky). Pokud si tedy různé položky mohou nést různé "své údaje" (bez ohledu na číselníky), a přitom se budou zavádět různé kombinace položka versus číselník, potom použijeme tento model. Můžeme totiž zavést nový typ položky (a strom číselníků to nepocítí) a naopak, můžeme zavést nový číselník a strom položek to nepocítí. Můžeme poté obě instance z nových typů poskládat přes vazbu horní úrovně. V tomto modelu je patrný vzor BRIDGE (viz ekniha "Design Patterns v OOP" nabízená na našem serveru). Model B, obrázek 2: Speciálním případem modelu A je situace, kdy model zjednodušíme tak, že vynecháme vazbu BRIDGE mezi abstraktními třídami a každý dědic položky si ukazuje na daný konkrétní číselník. To učiníme v případě, že víme, že pro daný číselník existuje pouze jeden typ položky. V tom případě lze model zjednodušit a nemusíme "přemosťovat" obě entity. Pokud však nastane požadavek zavádění kombinací "typ položky versus typ číselníku" (zde se předpokládá vztah těchto typů jedna ku jedné), potom nastanou tak trochu problémy - začnou se nám "kotit" třídy. Nejedná se o problém fatální (aplikace bude funkční), ale pro vývoj a údržbu velmi nepříjemná záležitost. Pro volbu tohoto modelu proto musíme posoudit, zda je opravdu vyloučen požadavek na vznik mnoha (dokonce neznámého počtu) kombinací "typ položky versus typ číselníku". Model C, obrázek 1: I tento zpočátku nepřijatelný model má své možné postavení v modelování. Představme si situaci, kdy analytik se na základě požadavků rozhodl pro model B (viz předešlý odstavec). Takto entity navrhl a takto přešel výsledek jeho práce do designu. Designér však (po dohodě s analytikem) zavedl ve své fázi mapování do designu krok optimalizace, takže se nemapuje 1:1 (co pojem, to tabulka a class v OOP), ale jinak. Jedná se de facto o degeneraci modelu za účelem získání technologických výhod (například rychlosti). Metodologie EFEM přikazuje, že tyto postupy optimalizace musejí vycházet z analytických modelů, které jsou vyjadřením přesné, a navíc se musí použít vzory optimalizace (postupy). Navíc musí být zachován (zdokumentován) jak původní analytický model, tak výsledný design model, ale také postup, jakým se mapování provedlo. Takže na obrázek 1 se můžeme dívat jako na realizovaný degenerovaný (optimalizovaný) design model, který vznikl z analytického modelu na obrázku 2. Nutno podotknout, že pojem "optimalizace" se nejeví jako příliš výstižný. Spíše se jedná o degeneraci (například v databázové teorii se jedná většinou o denormalizaci, rozpouštění entit, vazeb apod.). Každý krok optimalizace je totiž handlem něco za něco - zde například získáme rychlost, ovšem také získáme nevýhody uvedené v úvodu tohoto článku. Podotkněme, že pokud by designér opravdu obrázek 1 navrhl v systému na základě obrázku 2, potom provedl degeneraci (podle vzorů v EFEM) tak, že sloučil entity ve stromu generalizace - specializace do jedné (všechno z podtypů položek naskládal do horní třídy) a použil "přepínač" platnosti určitých sloupců. Navíc sloučil vazbu 1 ku N položek do jedné tak, že zavedl několik platných odkazů současně v jedné kombinaci. Flexibilní vazbu 1 : N (flexibilní co do počtu výskytů) nahradil jedním výskytem s natvrdo dopředu určenými itemy uvnitř výskytu (sloupce), z nichž (pro daný výskyt kombinace) platí pouze některé. "Výhodou" je, že vazbu 1:N, která vede ke joinu v RDB, nahradil jedním záznamem (provázanosti sloupců). Doslova "narovnal zalomenou vazbu" do jedné linky (datově záznamy v tabulce "pod sebou" položil "vedle sebe" do sloupců). Na otázku, který z těchto modelů použít, nelze jen tak odpovědět - v dané situaci musí analytik a designér rozhodnout. Co bych však jako vedoucí projektu považoval za vážnou chybu, kdyby se analytický model a design model nezdokumentoval a neznala se tak "čistá analytická verze" a realizovaná design verze. Systém se potom stává nepřehledným a nikdo neví, proč jsou věci tak, jak jsou. Důsledky jsou velmi nepříjemné: ztráta logiky modelů, ztráta transparence, ztráta přehledu o možnostech flexibility, neznalost původních analytických požadavků a následně rozpad vývojových prací, chaos v projektu. |