Nutnost použití vzoru OBSERVER pro zamezení nepříjemných

Transkript

Nutnost použití vzoru OBSERVER pro zamezení nepříjemných
Nutnost použití vzoru
OBSERVER pro zamezení
nepříjemných efektů
zpětných funkcionálních
vazeb mezi objekty
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ému článku s názvem Jedna z velmi častých a závažných chyb při
návrhu IS aneb jak vznikají tzv. "molochální systémy", (článek je volně ke
stažení na serveru www.objects.cz v sekci Odborné články zdarma) se „vřele
nedoporučuje“ vytvářet tzv. „molochální systémy“, které svou povahou jdou přesně
proti principům komponentní technologie: Na rozdíl od systému, který je logicky
navržen po vrstvách a který je následně je rozčleněn do odpovídajících fyzických
komponent, tak „molochální systém“ není fyzicky rozčleněný systém. Obsahuje
fyzicky „velká kusiska zdrojového kódu ke kompilaci“, nad kterými pracuje několik,
někdy i několik desítek vývojářů.
Systém „moloch“ není rozdělen fyzicky na menší části, které by se vzájemně
linkovaly (uses, include, import apod.). Protože „vše souvisí se vším“, tak systém
apriori nejde fyzicky rozdělit. Práce s takovým systémem je velmi nepříjemná.
Kompilace je nejenom technicky, ale hlavně organizačně náročná. Vývojáři se nad
kódem doslova bijí a dohody, které musí při zásahu do kódu neustále činit, silně
stěžují práci. Kód je sám o sobě netransparentní, nečitelný, se spoustou neobvyklých
a nečekaných vazeb. Každá i sebemenší změna nutně vede k zásahu do již
hotového celého kódu jako obrovského celku, což přináší další problémy.
Systém navržený jako moloch jde evidentně svou povahou přesně proti způsobu
tvorby IS pomocí moderních komponentních technologií (Java, DOT NET)…
Musím poznamenat, že tato situace je nepříjemná nejenom pro vývojáře, ale i pro
vedoucího pracovníka. Efektivní řízení vývojových prací nad molochem je v podstatě
nemožné anebo opravdu velmi obtížné s náběhem na žaludeční vředy.
Jedno z doporučení, jak se vyhnout návrhu systému s takovými vlastnostmi, spočívá
v tvorbě velmi dobrého a kvalitního analytického neboli konceptuálního modelu
s přísnou kontrolou „čistoty pojmů“. O tom detailněji pojednává zmíněný minulý
článek.
Ukazuje se však, že toto pravidlo je sice nutné, avšak není dostačující. Můžeme
vytvořit z hlediska čistoty pojmů opravdu dokonalý konceptuální model, můžeme
tento konceptuální logický model dobře rozvrstvit a následně se jej pokusit fyzicky
rozčlenit do relativně nepříliš velkých komponent v dané technologii…a přesto
zjistíme, že nastanou efekty příznačné pro kódovaného molocha!
V čem je tedy problém?
strana 2
© Ilja Kraval, 2007, http://www.objects.cz
Zpětné funkcionální vazby mezi
objekty
Odpověď spočívá v efektu chybně vyřešených zpětných funkcionálních vazeb.
Vysvětlíme si tento jev na jednoduché situaci:
Zpětná funkcionální vazba pro auta a barvy
Představme si, že některý objekt používá nějaký jiný objekt, má k němu přístup a
přitom tento druhý objekt první objekt nepoužívá, tj. vazba je evidentně jedním
směrem. Například se jedná o následující situaci v logickém modelu:
Každé evidované auto má barvu z číselníku barev, což zapíšeme pomocí jazyka
UML v modelu tříd takto:
Auto
+moje barva
Barva
1
obrázek 1 Zápis části logického modelu "auto má barvu z číselníku barev"
Směrovost vazby se samozřejmě promítne až do kódu a to v libovolném prostředí
jako jsou unity v Pascalu, knihovny v C++, assembly v C# …atd. Evidentně všechna
část kódu, která spravuje pravou část, tedy kód, který spravuje číselník barev, může
stát samostatně (například v jedné nebo několika komponentách), levá část kódu
(vše, co souvisí s auty), si bude „linkovat“ tuto pravou část. Můžeme si pomyslně
představit „střih“ systému, který rozdělí systém logicky a následně i fyzicky na dvě
části takto:
strana 3
© Ilja Kraval, 2007, http://www.objects.cz
Auto
+moje barva
Barva
1
obrázek 2 Představa o střihu systému v logice
Přitom je zřejmé, že „levá část“ používá „pravou část“.
Všimněme si důležité věci: Pravá část kódu (číselník barev) může stát samostatně,
nabízí své použití, tj. můžeme ji vyvinout jako první se vším všudy, tj. bez levé části
(aut), kterou vyvineme až následně. Vidíme evidentně vrstvy kódu jako vrstva vnitřní
a vrstva vnější (barvy a auta), to nám může pomoci vyhnout se molochálnímu
přístupu…
Připomeňme jenom závěr z předešlého článku, a totiž ten, že musíme dodržet čistotu
pojmů a do části kódu spravující barvy (vpravo od střihu) nesmíme „zaplantat“ nic
z aut, tedy jinak řečeno barvy musí zůstat barvami „čistými“, což má svou jasnou
logiku.
Vše se tedy zdá být jasné a logické, ale při pokusu o střih nás čeká jedno nemilé
překvapení: Co se stane, když se něco změní v evidenci barev a přitom ta evidovaná
auta, kterých se tato změna týká, na tuto změnu musí zareagovat? Jednoduchý
příklad: Potřebujeme vymazat z evidence nějakou barvu a všechna auta, která si na
ni ukazují, si mají ukazovat od té chvíle na „nic“. Je třeba podotknout, že daná
situace se nemusí vyskytnout pouze u tak jednoduchého případu, jako je reakce
nějakého prvku na vymazání. Úplně stejná situace nastává například při použití
asociační třídy, kdy prvek z asociační třídy musí zareagovat na změnu u prvku, jehož
instanci používá (tj. na kterou si ukazuje), anebo se může jednat o logický problém
v business logice, kdy někdo potřebuje zareagovat na nějakou změnu u toho, koho
„vidí“ (například reakce na změnu stavu v účtu, nebo reakce na novou historii u
prvku, apod.).
Při řešení těchto problémů se můžeme dopustit vážné chyby: Při běhu nějaké
funkcionality v barvách (ať už v kódu střední vrstvy anebo v databázi), kdy dojde ke
změně stavu v barvách a auta na tuto změnu stavu mají zareagovat, tak povinně
nesmí funkcionalita barev přímo zavolat funkcionalitu aut. To je totiž chybný postup
narušující předešlou logiku střihu a vnitřních a vnějších vrstev!
Tímto postupem, kdy voláme funkcionálně „zpět“, dojde ke zpětné funkcionální vazbě
proti směrovosti použití a vazba mezi auty a barvami se tak stane cirkulární. Velmi
nemilou záludností na této situaci je to, že v modelu tříd (který je statický), není tato
skutečnost zpětné funkcionální vazby na první pohled vůbec vidět…
strana 4
© Ilja Kraval, 2007, http://www.objects.cz
Závěr je jasný: Pokud funkcionalita barev zavolá funkcionalitu aut, potom se nám již
nepodaří oddělit tyto dvě vrstvy od sebe, protože jsou oboustranně cirkulárně
provázány (barvy volají a tedy potřebují auta) a pokud tento postup „zpětné
funkcionální vazby“ provedeme pokaždé a všude, kde potřebujeme vyřešit podobný
problém, máme zaděláno na molochální systém se všemi důsledky.
Otázkou tedy je, jak tuto situaci vyřešit správně. Ale než přistoupíme k odpovědi,
zmíníme se ještě o jednom velmi důležitém důsledku tohoto chybného postupu, který
souvisí s metodickými postupy ve vývoji.
Změna starého kódu při přidání
nového kódu?
Jedna z doporučovaných metodik vývoje středních, velkých případně rozsáhlých
informačních systémů se nazývá iterativní a inkrementální metoda. Velmi stručně
řečeno, tato metodika doporučuje u jen trochu větších systémů rozdělit systém na
menší části, vyřešit v jedné iteraci vývoje vždy jednu část a k ní poté přidat další části
řešení (inkrementace) a opět vyvinout tuto část v další iteraci, přidat další část
řešení…atd.
Asi nemusíme zdůrazňovat, že pokud dbáme na vrstvy systému, je tato metodika
mnohem schůdnější. Dovedeme si představit, že v předešlém příkladu bychom
nejprve v první iteraci vyvinuli vše, co se týká barev a poté vše, co se týká aut. Vrstvy
nám svou povahou nabízejí návod, odkud postupovat, kde začít, co na co navazuje.
Na druhou stranu, pokud nedbáme na vrstvy v systému (pochopitelně s důsledkem
tvorby molocha), iterativní a inkrementální metoda nebude fungovat dobře anebo
dokonce nebude fungovat vůbec.
Pro nás je nyní zajímavá tato situace: Představme si, že k již hotovému kódu
potřebujeme přidat nový kód. Může se jednat o jednu z těchto tří situací:
•
změnové řízení dané verze systému („na něco se pozapomnělo“),
•
vývoj nové verze („nová verze umí ještě něco navíc“)
•
nebo se jedná o novou iteraci v iterativní a inkrementální metodě vývoje („je
třeba přidat a řešit další část systému v další iteraci“)
Ať už se jedná libovolnou z těchto tří situací, tak důležité je zde slůvko přidat kód, tj.
máme na mysli situaci, kdy se nám logicky jeví, že se jedná o přidání nové
funkcionality beze změny původního již naprogramovaného kódu. Logicky by se nám
strana 5
© Ilja Kraval, 2007, http://www.objects.cz
mohlo zdát, že pokud přidáme nový kód s novou funkcionalitou, tak bychom se
nemuseli „hrabat“ ve starém kódu. Ano, pokud…
Tato logika bezesporu platí, avšak pouze v případě, že jsme dobře vyřešili problém
vrstev a také problém zpětné funkcionální vazby. Vraťme se k názornému a
jednoduchému příkladu s barvami a auty (viz obrázek 1). Nechť nějaká reakce na
změnu v evidenci barev se vyřešila chybně zpětným voláním funkcionality aut.
Symbolicky zapíšeme tuto situaci v pseudokódu takto:
Nějaká funkcionalita barev FB1;
{
//...něco zde běží a nastala změna stavu v barvách
// je třeba zavolat něco z aut:
zavolám nějakou funkcionalitu aut FA1;
// pokračuji dále v běhu...
}
Je zřejmé, že tato konstrukce bude fungovat a dokonce bude fungovat „bezchybně“,
ale bohužel konstrukce je špatná, protože část kódu barev volá něco z aut
(„plantáme“ auta do barev!). Tato čistě teoretická úvaha má dva praktické a velmi
nepříznivé důsledky. O prvém z nich už víme: Nelze od sebe striktně oddělit obě dvě
vrstvy, auta a barvy, přičemž častým používáním tohoto špatného řešení nám hrozí
tvorba molocha.
Druhý nepříznivý důsledek si uvědomíme, když přidáme novou entitu (doposud jsme
ji neřešili), označme ji jako X, která si také bude podobně jako auta ukazovat do
barev:
strana 6
© Ilja Kraval, 2007, http://www.objects.cz
+moje barva
Auto
Barva
1
1
+moje barva
X
obrázek 3 Přibyla další entita X, její prvky používají také barvy
Pro náš příklad je důležité, že způsob, jakým entita X přibyla do řešení, je jedna ze tří
zmíněných situací: buď se jedná o změnové řízení, nebo o novou verzi anebo o
novou iteraci. Jinak řečeno, důležitá je ta skutečnost, že starý kód je již hotov a
zkompilován, a my chceme přidat nový kód.
A máme zajímavý logický problém! Zdálo by se, že pouze přidáváme novou entitu a
proto se nemusíme „hrabat“ ve starém kódu. Avšak pokud se podíváme na
konstrukci volání funkcionalit v našem pseudokódu, tak pokud také prvky z X mají
zareagovat na tutéž změnu stavu jako auta (což bude velmi pravděpodobné), tak
musíme otevřít starý kód barev a přidat jeden řádek s voláním funkcionality X, která
(podobně jako u aut) ošetří reakci prvků X:
Nějaká funkcionalita barev FB1;
{
//...něco zde běží a nastala změna stavu v barvách
// je třeba zavolat něco z aut a něco z X:
zavolám nějakou funkcionalitu aut FA1;
zavolám nějakou funkcionalitu X FX1;
// pokračuji dále v běhu...
}
A co když někdo přijde s další entitou Y… a další a další entitou? Vidíme evidentní
nepříznivý důsledek číslo 2: Při tomto chybném řešení nastává efekt, že přidání kódu
znamená vždy otevření starého kódu a jeho změny…
strana 7
© Ilja Kraval, 2007, http://www.objects.cz
Řešení zpětné funkcionální vazby
mezi objekty
Existuje několik způsobů, jak vyřešit problém zpětné funkcionální vazby mezi objekty.
Všechny jsou postaveny na stejné myšlence, kterou si nyní vysvětlíme.
Podívejme se na předešlý kód se dvěma řádky volání funkcionalit a představme si,
že by se nám podařilo nějakou fintou zavolat nikoliv jako dva řádky, ale cyklem podle
schématu:
Cyklus čítač i od 1 do N
{
F[i] //zavolání i-té funkcionality
}
(v našem případě F[1]reprezentuje funkcionalitu aut FA1 a F[2]reprezentuje
funkcionalitu FX1 a pochopitelně N = 2)
V tom případě je problém evidentně vyřešen. Do seznamu funkcionalit můžeme
přidat klidně další a další funkcionality a původní kód cyklu se nemění. Jestliže
například někdo přijde s další entitou Y, starý kód nebudeme již otvírat (viz cyklus,
který se nemění), pouze je třeba novou funkcionalitu přidat do cyklu jako prvek
F[3].
Jedinou otázkou zůstává, jak toho efektu docílit. Existuje několik možností, jak
vytvořit cyklus z funkcionalit.
Volání funkce přes ukazatel na funkce
Jedna z možností, kterou nabízí strukturální programování, je vytvořit seznam
ukazatelů na funkce a s ním pracovat podle předešlého schématu. Pak skutečně
hovoříme o „i-té funkci“. Toto řešení bychom zvolili například v C jazyce (tj. v C bez
++).
Volání přes proměnnou typu funkce
Některé jazyky nabízejí předešlý způsob práce s ukazateli na funkce ve vyšší syntaxi
jazyka přímo deklarací proměnné typu funkce (například Pascal). Vytvořil by se
strana 8
© Ilja Kraval, 2007, http://www.objects.cz
seznam z takovýchto proměnných typu funkce a s nimi by se pracovalo podle
předešlého schématu.
Volání přes název funkce
Některá prostředí neumožňují práci s ukazateli a ani neznají objektové programování
ale poskytují možnost zavolat funkci přes její název uschovaný jako hodnota stringu
v nějaké proměnné. Nazvěme symbolicky takové volání jako
CallFunctionByName(název_funkce: string)
Funkce CallFunctionByName převezme hodnotu název funkce jako vstupní
parametr a poté zavolá funkci s názvem, který odpovídá hodnotě vstupního
parametru. Nyní stačí založit tabulku (obecně seznam) názvů funkcí a poté ji cyklem
přečíst a u každé načtené hodnoty zavolat CallFunctionByName. Přidat novou
funkci znamená přidat nový řetězec s odpovídajícím názvem do tabulky funkcí.
Volání přes delegáty
Jazyk C# umožňuje řešení této situace přes tzv. delegáty. Jedná se vlastně o řešení
seznamu odkazů na metody objektů. Princip je opět stejný…
Volání přes polymorfní metodu – použití vzoru OBSERVER (alias LISTENER)
Opravdu objektově čistým řešením je použití vzoru OBSERVER (v jazce JAVA se
nazývá LISTENER).
Poznámka: Problematiku návrhových vzorů a vzor OBSERVER si můžete podrobně
prostudovat v knize „Design patterns v OOP“ zdarma ke stažení na našem serveru.
Jak známo, vzor OBSERVER umožňuje zavést mechanismus sledování objektu tak,
že když tento objekt změní stav, ostatní objekty na tuto změnu zareagují, avšak
nedojde k přímé vazbě od sledovaného objektu k těmto objektům, tj. sledující a
reagující objekty jsou zavolány „nepřímo“ přes polymorfní metodu. Jednoduše
řečeno, vzor OBSERVER funguje tak, že naše funkcionality nebudeme schovávat ani
za ukazatel na funkci jako v C, ani za proměnnou typu funkce, ani za proměnnou
typu string s názvem funkce a ani za delegáta, ale „schováme“ ji za polymorfní
metodu (podrobně viz uvedená kniha).
strana 9
© Ilja Kraval, 2007, http://www.objects.cz
Závěr
Z uvedeného vyplývá, že pokud chceme správně vrstvit systém a zamezit cirkulárním
vazbám, musí se nutně použít vzor OBSERVER anebo použijeme nějakou jeho
obdobu. V každém případě je nutností odstínit přímé volání sledujícího objektu od
sledovaného objektu (tj. zamezit přímému volání proti směru statické vazby).
Při zanedbání tohoto pravidla jednak hrozí tvorba nepříjemných molochů (jako přímý
důsledek cirkulárních referencí) a kromě toho vzniká neblahý efekt, kdy přidání úplně
nového nezávislého kódu nutně vede k otevírání již existujícího starého kódu. To má
mimo jiné velmi nepříznivý vliv na postupy při změnovém řízení, při vývoji nové verze
a při použití iterativní a inkrementální metody vývoje.
Jak vidět, problematika tvorby vrstev v informačním systému je velmi důležitá a není
jednoduchá. Musím však z vlastní praxe poznamenat, že v mnoha SW firmách je
tento problém bohužel při návrhu IS velmi hrubě opomíjen a to se všemi důsledky.
Oproti tomu školení o modelování informačních systémů v UML, která jako autor
vedu, se zabývají podrobně jak syntaxí UML, tak se také věnují tzv. „doporučeným a
nedoporučeným postupům“. A konkrétně problematice, jak v systému vytvářet
necirkulární vrstvy (a následně „dobré“ komponenty), a jak se tedy vyhnout
molochům, se věnuje celá jedna velká kapitola i to se spoustou příkladů dobrých a
špatných postupů…
--- Konec článku ---
strana 10

Podobné dokumenty

návod - Hras

návod - Hras Připravte si svou prostorovou představivost. Pokaždé když zvolíte obtížnost, vygeneruje se nové bludistě, kterým procházíte tak, že otáčíte kostkou, jako by sama postupovala po čtverečkovaném papíř...

Více

Ukázka

Ukázka čedičových skalách. Bylo už po poledni, když přišli téměř pod vrchol. Tady Périer vykonal druhou část pokusu. Ve shodě s předpokladem Pascala rtuť v rource u vrcholu hory klesla o několik palců níž...

Více

Zeleznice_regionalni_rozvoj_CR

Zeleznice_regionalni_rozvoj_CR - naznačit možnosti, jak v současné době, kdy již železnice nemá na další rozvoj či úpadek regionů prakticky žádný dopad, zapojit železnici a její mechanismy do trvale udržitelného rozvoje regionů ...

Více

i v PDF vydání

i v PDF vydání Všechno se to pokazilo tehdy večer. Ani vlastně nikam jít nechtěl, ale když u něj zazvonil   Krestl, aby s ním vyrazil posedět do nějaké krčmy, nemohl vědět, co všechno se ten večer semele. Vyrazil...

Více

PDF - ukázka knihy - Computer Media sro

PDF - ukázka knihy - Computer Media sro na používaném operačním systému. Protože však operační systém Windows je v našich končinách většinou prvním systémem, se kterým se uživatel seznámí, často tápe při hledání volně dostupných aplikací...

Více