Nutnost použití vzoru OBSERVER pro zamezení nepříjemných efektů zpětných funkcionálních vazeb mezi objekty



Podobné dokumenty
Proč je analytický model IS nutným předpokladem pro zabránění tvorbě molochálních systémů

S KONFIGURACÍ POVOLENÝCH KOMBINACÍ DĚDICŮ

Odpověď na dotaz ohledně asociační třídy v modelu měření

NAUČTE SE MALOVAT SI INSTANCE!

ROZDÍL MEZI VZTAHEM EXTEND A INCLUDE V USE CASE DIAGRAMECH

Jak správně psát scénáře k případům užití?

Třetí část odpovědi na mail ohledně zpracování případů užití, aneb jak je to s číslováním pořadí případů užití

Jedna z velmi častých a závažných chyb při návrhu IS aneb jak vznikají tzv. molochální systémy

Druhá část odpovědi na mail ohledně zpracování případů užití

Vzor OBSERVER a jeho zajímavá varianta v kombinaci se vzorem ADAPTER Část 2

VYHLEDÁVÁNÍ PRVKŮ ACTOR A PROCESNÍ MODELOVÁNÍ

Šumperský efekt rozmnožení případů užití

Odpověď na dotaz ohledně asociační třídy v modelu měření

JEDNODUCHÁ A PRAKTICKÁ METODA ODHADU PRACNOSTI PROJEKTU (S UTILITOU KE STAŽENÍ ZDARMA)

Čtvrtá část odpovědi aneb jak je to vlastně s interakcí <<include>>

Principy OOP při tvorbě aplikací v JEE. Michal Čejchan

Úvod do objektově orientovaného programování s použitím jazyka C# pro střední školy

PB161 Programování v jazyce C++ Přednáška 7

PB161 Programování v jazyce C++ Přednáška 7

Úvod do programovacího jazyka Python

Úvod do programovacího jazyka Python

Problém identity instancí asociačních tříd

Úvod do principů objektově orientovaného programování

Výběr báze. u n. a 1 u 1

Hromadná korespondence

Kurz Postupy návrhu IS pomocí UML a OOP (5 dnů, in-house)

Delphi - objektově orientované

Vyřešené teoretické otázky do OOP ( )

IB111 Programování a algoritmizace. Programovací jazyky

TÉMATICKÝ OKRUH Softwarové inženýrství

10 Balíčky, grafické znázornění tříd, základy zapozdření

Unity a Objekty (NMIN102) RNDr. Michal Žemlička, Ph.D.

Analýza a Návrh. Analýza

Programujeme v softwaru Statistica

Polymorfismus. Časová náročnost lekce: 3 hodiny Datum ukončení a splnění lekce: 30.března

Typy souborů ve STATISTICA. Tento článek poslouží jako přehled hlavních typů souborů v programu

Kolekce ArrayList. Deklarace proměnných. Import. Vytvoření prázdné kolekce. napsal Pajclín

Statické proměnné a metody. Tomáš Pitner, upravil Marek Šabo

Úloha - rozpoznávání číslic

Funkcionální programování. Kristýna Kaslová

Projekt Obrázek strana 135

Zdroje chyb. Absolutní a relativní chyba. Absolutní chyba. Absolutní chyba přibližného čísla a se nazývá absolutní hodnota rozdílu přesného

PHP PHP je skriptovací programovací jazyk dynamických internetových stránek PHP je nezávislý na platformě

PB161 Programování v jazyce C++ Přednáška 4

Klasické metodiky softwarového inženýrství I N G M A R T I N M O L H A N E C, C S C. Y 1 3 A N W

TÉMATICKÝ OKRUH Softwarové inženýrství

Lokální definice (1) plocha-kruhu

Aplikované úlohy Solid Edge. SPŠSE a VOŠ Liberec. Radek Havlík [ÚLOHA 40 PODSESTAVY]

Aplikační vrstva. Úvod do Php. Ing. Martin Dostal

Programování II. Abstraktní třída Vícenásobná dědičnost 2018/19

2. Modelovací jazyk UML 2.1 Struktura UML Diagram tříd Asociace OCL. 3. Smalltalk 3.1 Jazyk Pojmenování

Lekce 12 Animovaný náhled animace kamer

DUM 06 téma: Tvorba makra pomocí VBA

Synchronizace CRM ESO9 a MS Exchange

Jak funguje element deep history v UML

1 Linearní prostory nad komplexními čísly

Derivace funkcí více proměnných

MATURITNÍ OTÁZKY ELEKTROTECHNIKA - POČÍTAČOVÉ SYSTÉMY 2003/2004 PROGRAMOVÉ VYBAVENÍ POČÍTAČŮ

EPLAN Electric P8 2.7 s databázemi na SQL serveru

Redakční systém Joomla. Prokop Zelený

Matice přechodu. Pozorování 2. Základní úkol: Určete matici přechodu od báze M k bázi N. Každou bázi napíšeme do sloupců matice, např.

Návrh IS - UML. Jaroslav Žáček

příručka modulu docházka v systém iškola.cz Adresa naší školy:

PHP framework Nette. Kapitola Úvod. 1.2 Architektura Nette

Vytvoření.NET komponenty (DLL) ve Visual Studiu

JE TŘEBA DBÁT NA ANONYMITU KLIENTA NEBO NE?

Robot BBC Micro:bit kódovaní v PXT Editoru

Questionnaire příručka uživatele

Programování II. Polymorfismus

6. Příkazy a řídící struktury v Javě

Vztah typu Extend v UML a jeho zvláštnosti

fakulty MENDELU v Brně (LDF) s ohledem na disciplíny společného základu (reg. č. CZ.1.07/2.2.00/28.

Koncept řešení EOS EVIDENCE ORGANIZAČNÍ STRUKTURY

Programování II. Dědičnost změna chování 2018/19

Modul msender message Sender. Brána do světa SMS zpráv a obchodní komunikace

4. Rekurze. BI-EP1 Efektivní programování Martin Kačer

Kritéria hodnocení praktické maturitní zkoušky z databázových systémů

Technologické postupy práce s aktovkou IS MPP

Definice. Vektorový prostor V nad tělesem T je množina s operacemi + : V V V, tj. u, v V : u + v V : T V V, tj. ( u V )( a T ) : a u V které splňují

Jak testovat software v praxi. aneb šetříme svůj vlastní čas

POČÍTAČE A PROGRAMOVÁNÍ

Návrh IS - UML. Jaroslav Žáček

ARITMETICKÉ OPERACE V BINÁRNÍ SOUSTAVĚ

Výchozí a statické metody rozhraní. Tomáš Pitner, upravil Marek Šabo

přetížení operátorů (o)

Vstupní požadavky, doporučení a metodické pokyny

Využití OOP v praxi -- Knihovna PHP -- Interval.cz

Výčtový typ strana 67

LDF MENDELU. Simona Fišnarová (MENDELU) Základy lineárního programování VMAT, IMT 1 / 25

Objektově orientované programování v jazyce Python

Příspěvkové organizace PAP (pomocný analytický přehled)

3. Je defenzivní programování technikou skrývání implementace? Vyberte jednu z nabízených možností: Pravda Nepravda

Registrační číslo projektu: CZ.1.07/1.5.00/ Elektronická podpora zkvalitnění výuky CZ.1.07 Vzděláním pro konkurenceschopnost

Řízení reálných projektů, agilní metodiky

Programujeme v softwaru Statistica

STŘEDNÍ ŠKOLA INFORMAČNÍCH TECHNOLOGIÍ A SOCIÁLNÍ PÉČE

12. Lineární programování

V 70. letech výzkumy četnosti výskytu instrukcí ukázaly, že programátoři a

Databáze I. 4. přednáška. Helena Palovská

Transkript:

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.

Ú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

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 1 Barva 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

Auto +moje barva 1 Barva 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

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

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

Auto +moje barva 1 Barva 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

Ř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

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

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