Při jedné konzultaci ve firmě, která vyvíjí technologické systémy, jsme narazili na zajímavý problém k řešení, který nakonec vedl k doporučení zavést logickou vrstvu aplikace a k použití vzoru BRIDGE.
Jak známo, existují dvě velmi rozdílné povahy vyvíjeného softwaru: Na jedné straně hovoříme o tzv. evidenčních systémech, také se nazývají jako business systémy resp. podnikové systémy. Jejich smyslem je podporovat přímo nějakou lidskou činnost, jmenujme například systémy pro účetnictví, pro řízení podniku, banky, pojišťovny atd. Business systémy bývají mnohdy velice rozsáhlé svou analytickou povahou (viz například banka se všemi svými agendami) a jsou postaveny většinou na standardních technologiích OOP + RDB (mnohdy se silnou podporou ORM) resp. post-relačních databázích (CACHE apod.).
Na druhé straně existují svou povahou odlišné systémy tzv. technologické, zvané také jako real-time systémy. Tyto aplikace nepodporují přímo lidskou činnost jako takovou, ale podporují buď software jiný (např. operační systémy, kryptografie, firewall apod.) anebo jsou vloženy do technologických zařízení a ta řídí (například řízení motorů, alarmy apod.) anebo které realizují technologickou komunikaci (jako jsou síťové komponenty, komunikace přes protokoly, zpracování streamů, packety apod.). Real-time systémy bývají oproti evidenčním velmi náročné a složité svými technologickými rysy, jako je požadovaná rychlost zpracování (mnohdy až extrémní), velmi omezená paměť, problémy paralelního zpracování apod. Z těchto důvodů bývají psány většinou v C++ nebo v C, někdy dokonce assembleru resp. podobných „nízkoúrovňových“ jazycích.
Pokud se nasazuje nějaký technologický systém jako celek (tj. nejedná se přímo o samostatnou technologickou komponentu), tak se většinou jedná o systém, který obsahuje povahu jak business systému, tak i real-time systému. Jako příklad uveďme aplikaci pro řízení celého velkého mrakodrapu: Takový systém se nutně skládá z evidenční části (např. evidence poschodí, pokojů, panelů, alarmů výtahů, uživatelů systému atd.), ale také musí obsahovat nutně i technologickou část (jako je ovládání výtahů, spouštění alarmů atd.). Podobně telefonická centrála hovorů realizuje komunikaci, ale současně musí podporovat evidenci účastníků hovoru, samotných hovorů, videomeetingů aj.
Je třeba podotknout, že i pro technologické systémy se efektivně nasazuje modelování v UML. Rozdíl oproti podnikovým systémům je pouze v tom, že některé z diagramů mají v technologických systémech větší uplatnění (např. STATE DIAGRAM, TIMING, apod.). Je třeba podotknout, že také i pro celou oblast řešení real-time systémů existují specifické návrhové vzory s podporou UML, viz například knihy od autora B. Douglasse.
Nedávno jsem byl účasten konzultace v jedné SW firmě, která vyvíjí systémy pro řízení motorů a podobných zařízení (tedy typické technologické systémy). Námi zkoumaný vyvíjený program jako příklad předmětu konzultace pro zlepšení návrhu SW byl psán v jazyce C. Během rozboru řešení jsme narazili na následující zajímavý problém.
V systému bylo nutné v určitých bodech měřit teplotu pomocí čidla. Označme toto čidlo jako X. Nechť v našem příkladu se pro čtení naměřené teploty tohoto čidla musela volat specifická funkce, nazvěme ji v našem příkladu jako GetTempX(). Pro naši lepší představu nechť tato funkce pochází z určité knihovny v C a čte přímo technologicky teplotu daného čidla například z určitého definovaného místa v paměti.
Při konzultaci jsme rozborem návrhu kódu zjistili, že programátoři v tom bodě programu, kde bylo třeba získat teplotu, si tuto zmíněnou funkci zavolali a získali tak požadovanou hodnotu aktuální teploty. To sice vypadá jako v pořádku (přece od toho tam ta funkce je), ale ne vše, co funguje dobře, je vždy dobrým řešením.
Pokud jsou nám známy vzory GOF z OOP, tak se nám určitě vybaví vzor BRIDGE. Zmíněná funkce GetTempX(), která čte přímo teplotu čidla, je totiž typickým příkladem funkce z části „implementace“ (alias Body) tohoto vzoru.
Poznámka: I když se jedná o vzor z objektově orientovaného programování, tak i v jazyce C můžeme tento vzor nasadit, pokud víme, jak lze v C „imitovat“ objektové prostředí.
V našem příkladu konzultace se vzor BRIDGE zavedl ve dvou krocích:
1. Krok:
Zavedení logické vrstvy aplikace
Pro získání teploty již nebudeme přistupovat přímo k implementační funkci čtoucí teplotu z čidla X, ale zavedeme vyšší prvek reprezentující obecný „logický teploměr“. V OOP (např. C++) by se jednalo o novou třídu a z ní nový objekt, v jazyce C o novou knihovnu s novou funkcí. Nazvěme tento nový prvek (ať už v C++ nebo v C) např. jako Thermometer.
Nový „vyšší“ prvek bude „obalovat“ původní prvek čtení teploty z čidla X a ve své implementaci jej bude volat. Okolní program (který ovládá zařízení resp. čte hodnoty), a který potřebuje načíst teplotu, musí od této chvíle přistupovat pouze k tomuto novému prvku a nijak jinak. K tomu mu slouží metoda / funkce nového prvku, nazvěme ji např. jako GetTemperature(). Tato metoda / funkce teprve ve své implementaci zavolá již zmíněnou funkci čtení z čidla X.
Navíc tuto implementační funkci čtení z čidla by bylo vhodné „schovat“ vůči okolnímu programu tak, aby programátor, který potřebuje někde načíst teplotu, ani omylem tuto implementační funkci čtení teploty z čidla nepoužil přímo, ale byl vždy donucen použít pouze vyšší prvek Thermometer s metodou GetTemperature(), viz obrázek:
obr.1: Volání okolí pro získání teploty od vyššího prvku
Tento nový zdánlivě zbytečný obal (jedná se vlastně pouze o jeden skok mezi voláním metody objektu / funkce navíc) vede k důležitému důsledku: Okolní program sice ví, odkud dostat teplotu (musí volat prvek Thermometer), ale neví, jak byla tato hodnota konkrétně načtena, protože způsob, jak byla teplota načtena, je pro okolí schován.
Je vidět, že již nyní se získala určitá vyšší flexibilita programu v tom smyslu, že lze implementaci daného prvku Thermometer mnohem snadněji vyměnit přímo jako celou funkci například za jiné čidlo resp. za simulátor hodnot teploty apod. aniž by se muselo zasahovat do původní funkce / metody čtení teploty z čidla X.
Tento krok odstínění implementace vyššími logickými prvky bychom měli provést u všech těch prvků, které nějakým způsobem obsahují implementační ovládání stojů a zařízení resp. u čtení hodnot, tedy nejen u teploměru. Vzniknou tak naprogramované prvky logické vrstvy jako jsou otáčkoměr, ovládání plynu, ovládání spojky aj. Způsob, jakým tyto prvky dosáhly své implementační dovednosti, je schován uvnitř těchto prvků pomocí implementační vrstvy.
Aplikace se tak rozdělila na dvě části, logickou a implementační (alias Handle / Body).
2.Krok
Aplikace vzoru BRIDGE
V předešlém odstavci padla zmínka o možnosti výměny implementace u logického prvku. Ano, to lze, ale postup by byl poměrně dost „tvrdý“: Museli bychom totiž uvnitř prvku z logické vrstvy (například v prvku Thermometer) vyměnit danou funkci čtoucí teplotu z čidla X za jinou tímto postupem:
- otevřeme kód prvku Thermometer,
- vyměníme volání metody / funkce případně vyměníme knihovnu, to vše editací kódu
- překompilujeme.
BTW: Tento postup snahy vyměnit kus kódu přepsáním editací je obecně vzato vynikajícím signálem pro nasazení polymorfismu, což je dobrý námět pro jiný článek.
Existuje však mnohem lepší programátorská konstrukce, která umožňuje výměnu této implementace bez zásahu do kódu a tou je v našem případě nasazení vzoru BRIDGE. I když vysvětlení tohoto vzoru bývá mnohdy velice složité, jeho podstata je velmi jednoduchá.
Doporučení vzoru BRIDGE zní: Učiňte implementační metody polymorfními a využijte tak flexibilitu polymorfismu pro výměnu implementace bez zásahu do původního kódu logických prvků. Logická vrstva a implementační se tak stanou nezávislými, tj. lze měnit jednu část (logickou resp. implementační) bez zásahu do druhé.
V našem případě to znamená, že naše implementační metoda čtoucí teplotu z čidla X se stane polymorfní a tedy dynamicky vyměnitelná bez zásahu do stávajícího kódu logického teploměru. Jak se tak učiní v C++ a C si nyní popíšeme.
V OOP (např. v C++) nejprve zavedeme interface společný pro všechny implementace čtení teplot z čidel (v C++ zavedeme třídu s abstraktní metodou). Následně se zavedou potomci jako implementace tohoto interfacu. Naše původní „tvrdá“ metoda čtoucí teplotu z čidla X se tak stane pouze jednou z množiny možných implementovaných metod definovaných v potomcích. Výměnou objektu potomka se také polymorfně vymění celá implementace jiného způsobu čtení teploty podle pravidla: „jiný potomek = jiné čidlo“ (anebo simulátor čtení teploty).
Zapsáno v UML by konkrétně tato situace v C++ vypadala nějak takto:
obr. 2: Class Diagram pro řešení pomocí vzoru BRIDGE
V jazyce C bez objektů se dá dosáhnout stejného efektu polymorfního chování voláním funkce nikoliv „natvrdo“, ale přes ukazatel na funkci resp. přes proměnnou typu funkce. Znamená to, že daná implementační funkce čtoucí teplotu z čidla X by se uvnitř teploměru nevolala přímo, ale byla by „schována“ za ukazatel (resp. proměnou typu funkce) a byla by do této pozice volání dosazena dynamicky při inicializaci dané instance teploměru.
Tímto postupem mohou tak pro stejný kód teploměru dynamicky vznikat různé varianty teploměru s různou v inicializaci dosazenou implementací. Jeden logický kód teploměru bude tedy obsluhovat různé typy implementace teploměrů podle toho, jak bude teploměr dynamicky inicializován ve své implementaci, tj. podle toho, jaká implementační metoda / funkce čtoucí teplotu bude dosazena do odpovídajícího volání čtení teploty.
Poznámka: Existuje několik možných způsobů, jak inicializovat daný prvek dosazením implementace, buď pomocí dosazení prvku z vně (obdoba DEPENDENCY INJECTION) nebo pomocí přímé volby převodníkem kódu třídy (obdoba FACTORY), což je dobrý námět pro příští článek.
Samozřejmě teplotní čidlo je pouze jeden příklad v celém systému, tento způsob oddělení a současně přemostí části logické a implementační přes interface (v C++) resp. přes volání funkcí přes ukazatel (v C), provedeme u všech logických prvků (otáčkoměr, spojka, plyn), takže i ony budou mít možnost dynamicky změnit „způsob čtení hodnot“ resp. „ovládání zařízení“ ve všech možných variantách a kombinacích implementací.
Je zřejmé, že, co se týče výměny implementační části, tímto postupem dosáhneme maximální flexibility. Umíme si představit, že by se například do teploměru dosadil simulátor určitého chodu teploty v čase. Pro testovací účely by se naprogramoval simulátor chodu teploty v časové řadě např. stoupající až přes kritickou mez a tento simulátor čidla by se dosadil za implementaci místo původního čtení teploty z čidla X.
Závěr
V daném příkladu byl ukázán praktický postup oddělení logické vrstvy real-time aplikace a implementační části včetně nasazení vzoru BRIDGE.
konec článku
Napsat komentář