UML-állapotgép
Az UML-állapotgép[1] más néven UML-állapotdiagram, a véges automata matematikai koncepciójának kiterjesztése a számítástechnikai alkalmazásokban, ahogyan azt az Unified Modeling Language (UML) jelölése fejezi ki.
A mögöttes koncepciók arra irányulnak, hogy megszervezzék egy eszköz, számítógépes program vagy más (gyakran technikai) folyamat működését úgy, hogy egy entitás vagy annak minden alentitása mindig pontosan egy lehetséges állapotban legyen, és hogy ezek között az állapotok között jól meghatározott feltételes átmenetek legyenek.
Az UML-állapotgép a Harel-állapotdiagramok objektumalapú változata, amelyet az UML adaptált és kibővített.[2][3] Az UML-állapotgépek célja a hagyományos véges állapotgépek fő korlátainak leküzdése, miközben megőrzik azok fő előnyeit. Az UML-állapotdiagramok bevezetik a hierarchikusan beágyazott állapotok és az ortogonális régiók új fogalmait, miközben kiterjesztik az akciók fogalmát. Az UML-állapotgépek a Mealy gépek és a Moore gépek jellemzőit is magukban hordozzák. Támogatják azokat az akciókat, amelyek mind a rendszer állapotától, mind a kiváltó eseménytől függenek, mint a Mealy gépeknél, valamint a belépési és kilépési akciókat, amelyek állapotokhoz, nem pedig átmenetekhez kapcsolódnak, mint a Moore gépeknél.[4]
Az "UML-állapotgép" kifejezés kétféle állapotgépre utalhat: viselkedési, illetve protokoll-állapotgépekre. A viselkedési állapotgépek egyedi entitások (pl. osztálypéldányok), egy alrendszer, egy csomag vagy akár egy teljes rendszer viselkedésének modellezésére használhatók. A protokoll állapotgépeket a használati protokollok kifejezésére használják, és osztályozók, interfészek és portok legális használati forgatókönyveinek meghatározására alkalmazhatók.
Alapvető állapotgép fogalmak
[szerkesztés]Sok szoftverrendszer eseményvezérelt, ami azt jelenti, hogy folyamatosan várnak valamilyen külső vagy belső esemény bekövetkezésére, mint például egy egérkattintás, egy gombnyomás, egy időjel vagy egy adatcsomag érkezése. Az esemény felismerése után az ilyen rendszerek azáltal reagálnak, hogy elvégzik a megfelelő számítást, amely magában foglalhatja a hardver kezelését vagy olyan „puha” események generálását, amelyek más belső szoftverkomponenseket indítanak el. (Ezért hívják az eseményvezérelt rendszereket alternatívan reaktív rendszereknek is.) Miután az eseménykezelés befejeződött, a rendszer visszatér a következő esemény várakozásához.
Az eseményre adott válasz általában az esemény típusától és a rendszer belső állapotától függ, és magában foglalhat egy állapotváltozást, amely állapotátmenetet eredményez. Az események, állapotok és az ezek között zajló állapotátmenetek mintája absztrahálható és véges állapotgépként (FSM) ábrázolható.
Az FSM koncepciója fontos az eseményvezérelt programozásban, mert az eseménykezelést kifejezetten az esemény típusától és a rendszer állapotától teszi függővé. Ha helyesen használják, egy állapotgép drasztikusan csökkentheti a kódon keresztüli végrehajtási útvonalak számát, egyszerűsítheti az egyes elágazási pontokon tesztelt feltételeket, és megkönnyítheti a különböző végrehajtási módok közötti váltást.[5] Ezzel szemben, ha az eseményvezérelt programozást egy mögöttes FSM modell nélkül alkalmazzák, a programozók hajlamosak hibára hajlamos, nehezen bővíthető és túlzottan összetett alkalmazáskódot előállítani.[6]
Alapvető UML-állapotdiagramok
[szerkesztés]Az UML megőrzi a hagyományos állapotdiagramok általános formáját. Az UML-állapotdiagramok irányított gráfok, amelyekben a csomópontok az állapotokat, a kapcsolók pedig az állapotátmeneteket jelölik. Például, az 1. ábra egy UML-állapotdiagramot mutat be, amely a számítógép billentyűzetének állapotgépéhez tartozik. Az UML-ben az állapotokat lekerekített téglalapokként ábrázolják, amelyek állapotnevekkel vannak feliratozva. Az átmeneteket nyilakként ábrázolják, amelyeket a kiváltó események címkéznek, amelyeket opcionálisan a végrehajtott akciók listája követ. A kezdeti átmenet a szilárd körből indul ki, és meghatározza az alapértelmezett állapotot, amikor a rendszer először elindul. Minden állapotdiagramnak rendelkeznie kell ilyen átmenettel, amelyet nem kell címkézni, mivel nem esemény váltja ki. A kezdeti átmenethez akciók is társíthatók.
Események
[szerkesztés]Az esemény olyan esemény, amely hatással van a rendszerre. Szigorúan véve, az UML specifikációban[1] az esemény kifejezés az előfordulás típusára utal, nem pedig annak bármely konkrét előfordulására. Például a Keystroke egy esemény a billentyűzet számára, de egy gomb minden egyes lenyomása nem esemény, hanem a Keystroke esemény konkrét példánya. Egy másik érdekes esemény a billentyűzet számára a bekapcsolás lehet, de ha holnap 10:05:36-kor bekapcsoljuk, az csak egy példa a bekapcsolási eseményre.
Egy eseményhez kapcsolódhatnak paraméterek, amelyek lehetővé teszik, hogy az eseménypéldány ne csak egy érdekes esemény bekövetkezését közvetítse, hanem mennyiségi információkat is az eseményről. Például a számítógép billentyűzetén egy billentyű lenyomásával generált Keystroke eseményhez kapcsolódó paraméterek tartoznak, amelyek közvetítik a karakter szkennelési kódját, valamint a Shift, Ctrl és Alt billentyűk állapotát.
Egy eseménypéldány túllépi azt a pillanatnyi eseményt, amely létrehozta, és közvetítheti ezt az eseményt egy vagy több állapotgép számára. Miután létrejött, az eseménypéldány egy feldolgozási életcikluson megy keresztül, amely akár három szakaszból is állhat. Először, az eseménypéldányt fogadják, amikor azt elfogadják és várakozik a feldolgozásra (például elhelyezik az eseménysorban). Később az eseménypéldányt továbbítják az állapotgéphez, ekkor válik az aktuális eseménnyé. Végül, amikor az állapotgép befejezi az eseménypéldány feldolgozását, azt elfogyasztják. Egy elfogyasztott eseménypéldány már nem áll rendelkezésre a feldolgozásra.
Állapotok
[szerkesztés]Minden állapotgépnek van egy állapota, amely meghatározza az állapotgép reakcióját az eseményekre. Például, amikor lenyom egy billentyűt a billentyűzeten, a generált karakterkód lehet nagybetűs vagy kisbetűs, attól függően, hogy a Caps Lock aktív-e. Ezért a billentyűzet viselkedése két állapotra osztható: "alapértelmezett" állapot és "caps_locked" állapot. (A legtöbb billentyűzeten van egy LED, amely jelzi, hogy a billentyűzet "caps_locked" állapotban van.) A billentyűzet viselkedése csak a történetének bizonyos aspektusaitól függ, nevezetesen attól, hogy megnyomták-e a Caps Lock billentyűt, de például nem attól, hogy hány és pontosan melyik más billentyűket nyomták le korábban. Egy állapot képes absztrahálni minden lehetséges (de lényegtelen) eseménysorozatot, és csak a relevánsakat rögzíteni.
A szoftverállapotgépek (és különösen a klasszikus FSM-ek) kontextusában az állapot kifejezést gyakran egyetlen állapotváltozóként értelmezik, amely csak korlátozott számú előre meghatározott értéket vehet fel (például két értéket a billentyűzet esetében, vagy általánosabban - valamilyen enum típusú változót sok programozási nyelvben). Az állapotváltozó (és a klasszikus FSM modell) elképzelése az, hogy az állapotváltozó értéke teljes mértékben meghatározza a rendszer aktuális állapotát bármely adott időpontban. Az állapot koncepciója leegyszerűsíti a végrehajtási kontextus azonosításának problémáját a kódban azáltal, hogy csak az állapotváltozót kell vizsgálni sok változó helyett, így kiküszöbölve rengeteg feltételes logikát.
Kiterjesztett állapotok
[szerkesztés]A gyakorlatban azonban az állapotgép teljes állapotának egyetlen állapotváltozóként való értelmezése gyorsan impraktikussá válik minden egyszerűnél bonyolultabb állapotgép esetében. Valójában még ha csak egyetlen 32 bites egész számot használunk is az állapotgép állapotának leírására, ez több mint 4 milliárd különböző állapotot eredményezhet, ami korai állapotrobbanáshoz vezet. Ez az értelmezés nem praktikus, így az UML-állapotgépekben az állapotgép teljes állapota általában két részre oszlik: (a) egy enumerált állapotváltozóra és (b) minden más változóra, amelyeket kiterjesztett állapotnak nevezünk. Másképpen fogalmazva, az enumerált állapotváltozót a teljes állapot minőségi aspektusaként, míg a kiterjesztett állapotot a mennyiségi aspektusokként értelmezhetjük. Ebben az értelmezésben a változó megváltozása nem mindig jelenti a rendszer viselkedésének minőségi aspektusainak változását, és ezért nem vezet állapotváltozáshoz.[7]
Az állapotgépeket, amelyeket kiterjesztett állapotváltozókkal egészítettek ki, kiterjesztett állapotgépeknek nevezik, és az UML-állapotgépek ebbe a kategóriába tartoznak. A kiterjesztett állapotgépek az alapul szolgáló formalizmust sokkal összetettebb problémákra tudják alkalmazni, mint amit a kiterjesztett állapotváltozók nélkül praktikus lenne. Például, ha egy korlátot kell megvalósítanunk az FSM-ben (például a billentyűleütések számának korlátozása 1000-re), akkor kiterjesztett állapot nélkül 1000 állapotot kellene létrehoznunk és feldolgoznunk, ami nem praktikus; azonban egy kiterjesztett állapotgéppel bevezethetünk egy key_count változót, amelyet 1000-re inicializálunk és minden egyes billentyűleütéssel csökkentünk, anélkül, hogy az állapotváltozó megváltozna.
A 2. ábrán látható állapotdiagram egy kiterjesztett állapotgép példája, amelyben a rendszer teljes állapota (amit kiterjesztett állapotnak nevezünk) egy minőségi aspektus—a állapotváltozó—és a mennyiségi aspektusok—a kiterjesztett állapotváltozók kombinációja.
A kiterjesztett állapotgépek nyilvánvaló előnye a rugalmasság. Például a key_count által meghatározott korlát 1000-ről 10000 leütésre való módosítása egyáltalán nem bonyolítaná a kiterjesztett állapotgépet. Az egyetlen szükséges módosítás a key_count kiterjesztett állapotváltozó inicializálási értékének megváltoztatása lenne az inicializálás során.
A kiterjesztett állapotú gépek e rugalmasságának azonban ára van a kiterjesztett állapot "minőségi" és "mennyiségi" aspektusa közötti összetett kapcsolat miatt. A csatolás az átmenetekhez csatolt védőfeltételeken keresztül történik, amint az a 2. ábrán látható.
Őrfeltételek
[szerkesztés]Az őrfeltételek (vagy egyszerűen őrök) logikai kifejezések, amelyeket dinamikusan értékelnek ki a kiterjesztett állapotváltozók és az eseményparaméterek értékei alapján. Az őrfeltételek befolyásolják az állapotgép viselkedését azáltal, hogy akciókat vagy átmeneteket csak akkor engedélyeznek, ha értékük TRUE, és letiltják őket, ha értékük FALSE. Az UML-jelölésmódban az őrfeltételeket szögletes zárójelek között tüntetik fel (például [key_count == 0] a 2. ábrán).
Az őrfeltételek szükségessége azonnali következménye annak, hogy memória kiterjesztett állapotváltozókat adunk az állapotgép formalizmusához. Mértékkel használva a kiterjesztett állapotváltozók és az őrfeltételek egy erőteljes mechanizmust alkotnak, amely egyszerűsítheti a tervezést. Másrészt, a kiterjesztett állapotok és őrfeltételek könnyen túlzottan használhatók.[8]
Műveletek és átmenetek
[szerkesztés]Amikor egy eseménypéldány továbbításra kerül, az állapotgép akciók végrehajtásával reagál, mint például egy változó megváltoztatása, I/O műveletek végrehajtása, egy függvény meghívása, egy másik eseménypéldány generálása, vagy egy másik állapotba való átlépés. Az aktuális eseményhez kapcsolódó bármilyen paraméterérték elérhető az összes olyan akció számára, amelyet közvetlenül az az esemény vált ki.
Az egyik állapotból a másikba való átlépést állapotátmenetnek nevezzük, és az eseményt, amely ezt okozza, kiváltó eseménynek, vagy egyszerűen triggernek hívjuk. A billentyűzet példájában, ha a billentyűzet "alapértelmezett" állapotban van, amikor a CapsLock billentyűt lenyomják, a billentyűzet "caps_locked" állapotba lép. Azonban, ha a billentyűzet már "caps_locked" állapotban van, a CapsLock lenyomása egy másik átmenetet okoz—"caps_locked" állapotból "alapértelmezett" állapotba. Mindkét esetben a CapsLock lenyomása a kiváltó esemény.
A kiterjesztett állapotgépekben egy átmenetnek lehet őrfeltétele, ami azt jelenti, hogy az átmenet csak akkor "sülhet el", ha az őrfeltétel értékelése TRUE. Egy állapotnak sok átmenete lehet ugyanarra a kiváltó eseményre válaszul, amennyiben ezeknek az őrfeltételei nem fedik át egymást; azonban ez a helyzet problémákat okozhat az őrfeltételek értékelési sorrendjében, amikor a közös trigger bekövetkezik. Az UML specifikáció[1] szándékosan nem ír elő semmilyen konkrét sorrendet; ehelyett az UML a tervezőre bízza, hogy olyan módon alakítsa ki az őrfeltételeket, hogy azok értékelési sorrendje ne legyen lényeges. Gyakorlatilag ez azt jelenti, hogy az őrfeltételek kifejezéseinek nem lehetnek mellékhatásai, legalábbis olyanok nem, amelyek befolyásolnák más, ugyanazon kiváltó eseménnyel rendelkező őrfeltételek értékelését.
Futtatástól a befejezésig végrehajtási modell
[szerkesztés]Minden állapotgép-formalizmus, beleértve az UML-állapotgépeket is, egyetemesen feltételezi, hogy az állapotgép minden esemény feldolgozását befejezi, mielőtt a következő esemény feldolgozását megkezdené. Ezt a végrehajtási modellt "run to completion" (RTC) modellnek nevezik.
Az RTC modellben a rendszer az eseményeket diszkrét, oszthatatlan RTC lépésekben dolgozza fel. Az új bejövő események nem szakíthatják meg az aktuális esemény feldolgozását, és tárolni kell őket (általában egy eseménysorban), amíg az állapotgép ismét tétlen nem lesz. Ezek a szemantikai szabályok teljesen elkerülik a belső párhuzamossági problémákat egyetlen állapotgépen belül. Az RTC modell szintén megkerüli az átmenetekhez kapcsolódó akciók feldolgozásának fogalmi problémáját, amikor az állapotgép nincs jól meghatározott állapotban (két állapot között van) az akció végrehajtásának ideje alatt. Az eseményfeldolgozás során a rendszer nem reagál (nem megfigyelhető), így az az idő alatt fennálló rosszul meghatározott állapotnak nincs gyakorlati jelentősége.
Fontos megjegyezni, hogy az RTC nem jelenti azt, hogy az állapotgépnek monopolizálnia kell a CPU-t, amíg az RTC lépés be nem fejeződik. A megszakítási korlátozás csak az eseményeket már feldolgozó állapotgép feladatkörnyezetére vonatkozik. Egy multitasking környezetben más feladatok (amelyek nem kapcsolódnak a foglalt állapotgép feladatkörnyezetéhez) futtathatók, esetleg megszakítva a jelenleg végrehajtó állapotgépet. Amíg más állapotgépek nem osztanak meg változókat vagy egyéb erőforrásokat egymással, nincsenek párhuzamossági veszélyek.
UML kiterjesztések a hagyományos FSM formalizmushoz
[szerkesztés]Bár a hagyományos FSM-ek kiváló eszközt jelentenek kisebb problémák kezelésére, általánosan ismert, hogy hajlamosak kezelhetetlenné válni, még mérsékelten összetett rendszerek esetén is. Az állapotok és átmenetek robbanásának nevezett jelenség miatt a hagyományos FSM-ek komplexitása sokkal gyorsabban nő, mint az általuk leírt rendszer komplexitása. Ez azért történik, mert a hagyományos állapotgép formalizmusa ismétlődéseket idéz elő. Például, ha megpróbálja egy egyszerű zsebszámológép viselkedését hagyományos FSM-mel ábrázolni, azonnal észreveszi, hogy sok eseményt (pl. a Clear vagy Off gomb megnyomását) sok állapotban azonos módon kell kezelni. A hagyományos FSM, ahogy az alábbi ábra mutatja, nem képes az ilyen közös jellemzők megragadására, és megköveteli ugyanazon akciók és átmenetek ismétlését sok állapotban. Ami hiányzik a hagyományos állapotgépekből, az az általános viselkedés kivonásának mechanizmusa, amely lehetővé tenné, hogy sok állapot között megosszák.
Az UML-állapotgépek pontosan ezt a hagyományos véges állapotgépek (FSM-ek) hiányosságát kezelik. Számos funkciót kínálnak az ismétlések kiküszöbölésére, így egy UML-állapotgép komplexitása már nem növekszik robbanásszerűen, hanem hűen tükrözi a leírt reaktív rendszer komplexitását. Nyilvánvaló, hogy ezek a funkciók nagyon érdekesek a szoftverfejlesztők számára, mert csak ezek teszik az állapotgép megközelítést valóban alkalmazhatóvá a valós problémákra.
Hierarchikusan beágyazott állapotok
[szerkesztés]Az UML-állapotgépek legfontosabb újítása a hagyományos FSM-ekhez képest a hierarchikusan beágyazott állapotok bevezetése (ezért nevezik az állapotdiagramokat hierarchikus állapotgépeknek vagy HSM-eknek is). Az állapotbeágyazáshoz kapcsolódó szemantika a következő (lásd a 3. ábrát): Ha egy rendszer a beágyazott állapotban van, például "eredmény" (amit alállapotnak nevezünk), akkor (implicit módon) a környező "be" állapotban (amit szuperállapotnak nevezünk) is van. Ez az állapotgép megkísérli az eseményt az alállapot kontextusában kezelni, amely fogalmilag a hierarchia alsó szintjén helyezkedik el. Azonban, ha az "eredmény" alállapot nem írja elő az esemény kezelését, az esemény nem csendben eldobásra kerül, mint egy hagyományos "lapos" állapotgépben; ehelyett automatikusan a magasabb szintű "be" szuperállapot kontextusában kezelik. Ez jelenti azt, hogy a rendszer egyszerre van "eredmény" és "be" állapotban. Természetesen az állapotbeágyazás nem korlátozódik egyetlen szintre, és az egyszerű eseményfeldolgozási szabály rekurzívan alkalmazható bármely beágyazási szintre.
Azokat az állapotokat, amelyek más állapotokat tartalmaznak, összetett állapotoknak nevezzük; ezzel szemben azokat az állapotokat, amelyeknek nincs belső szerkezete, egyszerű állapotoknak hívjuk. Egy beágyazott állapotot közvetlen alállapotnak nevezünk, ha azt nem tartalmazza semmilyen más állapot; egyébként tranzitívan beágyazott alállapotnak nevezzük.
Mivel egy összetett állapot belső szerkezete tetszőlegesen összetett lehet, bármely hierarchikus állapotgép tekinthető egy (magasabb szintű) összetett állapot belső szerkezetének. Fogalmilag kényelmes egy összetett állapotot az állapotgép hierarchia végső gyökereként meghatározni. Az UML specifikációban[1] minden állapotgépnek van egy felső állapota (minden állapotgép hierarchiájának absztrakt gyökere), amely az állapotgép összes többi elemét tartalmazza. Ennek az összes elemet magában foglaló felső állapotnak a grafikus ábrázolása opcionális.
Amint látható, a hierarchikus állapotbontás szemantikáját úgy tervezték, hogy megkönnyítse a viselkedés újrafelhasználását. Az alállapotoknak (beágyazott állapotoknak) csak a szuperállapotoktól (tartalmazó állapotoktól) való eltéréseket kell meghatározniuk. Egy alállapot könnyedén örökölheti[6] a szuperállapot(ok) közös viselkedését azáltal, hogy egyszerűen figyelmen kívül hagyja a közösen kezelt eseményeket, amelyeket ezután automatikusan a magasabb szintű állapotok kezelnek. Más szavakkal, a hierarchikus állapotbeágyazás lehetővé teszi a különbség alapján történő programozást.[9]
Az állapothierarchia leggyakrabban hangsúlyozott aspektusa az absztrakció—egy régi és hatékony technika a komplexitás kezelésére. Ahelyett, hogy egy komplex rendszer minden aspektusát egyszerre kezelnék, gyakran lehetséges figyelmen kívül hagyni (absztrahálni) a rendszer egyes részeit. A hierarchikus állapotok ideális mechanizmust kínálnak a belső részletek elrejtésére, mivel a tervező könnyedén nagyíthat vagy kicsinyíthet, hogy elrejtse vagy megjelenítse a beágyazott állapotokat.
Az összetett állapotok nemcsak a komplexitást rejtik el, hanem aktívan csökkentik is a hierarchikus eseményfeldolgozás erőteljes mechanizmusa révén. Ilyen újrafelhasználás nélkül már a rendszer komplexitásának mérsékelt növekedése is az állapotok és átmenetek számának robbanásszerű növekedéséhez vezethetne. Például, a zsebszámológépet reprezentáló hierarchikus állapotgép (lásd a 3. ábrát) elkerüli a Clear és Off átmenetek megismétlését gyakorlatilag minden állapotban. Az ismétlések elkerülése lehetővé teszi, hogy a HSM-ek növekedése arányos maradjon a rendszer komplexitásának növekedésével. Ahogy a modellezett rendszer növekszik, az újrafelhasználás lehetősége is növekszik, így potenciálisan ellensúlyozza az állapotok és átmenetek számának aránytalan növekedését, amely a hagyományos FSM-ekre jellemző.
Ortogonális régiók
[szerkesztés]A hierarchikus állapotbontás elemzése magában foglalhatja az "exclusive-OR" művelet alkalmazását bármely adott állapotra. Például, ha egy rendszer az "on" szuperállapotban van (lásd a 3. ábrát), előfordulhat, hogy az "operand1" alállapotban VAGY az "operand2" alállapotban VAGY az "opEntered" alállapotban VAGY az "result" alállapotban van. Ez azt eredményezné, hogy az "on" szuperállapotot egy 'OR-állapotként' írjuk le.
Az UML-állapotdiagramok bevezetik a kiegészítő ÉS-felbontást is. Ez a felbontás azt jelenti, hogy egy összetett állapot két vagy több ortogonális régiót tartalmazhat (ebben a kontextusban az ortogonális jelentése kompatibilis és független), és hogy egy ilyen összetett állapotban való tartózkodás egyidejűleg az összes ortogonális régióban való tartózkodást jelenti.[10]
Az ortogonális régiók megoldják azt a gyakori problémát, amikor a rendszer viselkedése független, egyidejűleg aktív részekre bomlik, ami az állapotok számának kombinatorikus növekedéséhez vezet. Például egy számítógép billentyűzetének a fő billentyűzeten kívül van egy független numerikus billentyűzete is. Az előző beszélgetésből emlékezzünk vissza a fő billentyűzet két azonosított állapotára: "alapértelmezett" és "caps_locked" (lásd az 1. ábrát). A numerikus billentyűzet is két állapotban lehet—"számok" és "nyilak"—attól függően, hogy a Num Lock aktív-e. A billentyűzet teljes állapottere a standard felosztásban ezért a két komponens (fő billentyűzet és numerikus billentyűzet) Descartes-szorzata, és négy állapotból áll: "alapértelmezett–számok," "alapértelmezett–nyilak," "caps_locked–számok," és "caps_locked–nyilak." Azonban ez természetellenes ábrázolás lenne, mert a numerikus billentyűzet viselkedése nem függ a fő billentyűzet állapotától és fordítva. Az ortogonális régiók használata lehetővé teszi a független viselkedések Descartes-szorzatként való összekeverésének elkerülését, és ehelyett különállóan maradnak, ahogyan az a 4. ábrán látható.
Vegyük észre, hogy ha az ortogonális régiók teljesen függetlenek egymástól, akkor kombinált komplexitásuk egyszerűen additív, ami azt jelenti, hogy a rendszer modellezéséhez szükséges független állapotok száma egyszerűen k + l + m + ... , ahol k, l, m, ... az egyes ortogonális régiókban lévő OR-állapotok számát jelenti. Azonban a kölcsönös függőség általános esete multiplikatív komplexitást eredményez, így általában a szükséges állapotok száma k × l × m × ... szorzat.
A legtöbb valós helyzetben az ortogonális régiók csak hozzávetőlegesen ortogonálisak (azaz nem teljesen függetlenek). Ezért az UML-állapotdiagramok számos módot kínálnak az ortogonális régiók közötti kommunikációra és viselkedésük szinkronizálására. Ezek közül a gazdag (néha összetett) mechanizmusok közül talán a legfontosabb jellemző, hogy az ortogonális régiók képesek viselkedésüket koordinálni úgy, hogy esemény példányokat küldenek egymásnak.
Bár az ortogonális régiók a végrehajtás függetlenségét implikálják (lehetővé téve többé-kevésbé a párhuzamosságot), az UML specifikáció nem követeli meg, hogy minden ortogonális régióhoz külön végrehajtási szálat rendeljenek (bár ez megtehető, ha kívánatos). Valójában leggyakrabban az ortogonális régiók ugyanazon a szálon belül hajtják végre a feladatokat.[11] Az UML specifikáció csak azt írja elő, hogy a tervező ne támaszkodjon semmilyen konkrét sorrendre az események releváns ortogonális régiókba történő továbbításakor.
Be- és kilépési műveletek
[szerkesztés]Minden állapot az UML-állapotdiagramokban rendelkezhet opcionális belépési akciókkal, amelyeket az állapotba való belépéskor hajtanak végre, valamint opcionális kilépési akciókkal, amelyeket az állapotból való kilépéskor hajtanak végre. A belépési és kilépési akciók állapotokhoz kapcsolódnak, nem átmenetekhez. Függetlenül attól, hogy egy állapot hogyan kerül be vagy ki, minden belépési és kilépési akció végrehajtásra kerül. Ezen tulajdonság miatt az állapotdiagramok úgy viselkednek, mint a Moore-gépek. Az UML jelölése az állapot belépési és kilépési akcióira az, hogy az állapot neve alatti részben elhelyezik a "entry" (vagy "exit") fenntartott szót, ezt követi a perjel és az akciók tetszőleges listája (lásd az 5. ábrát).
A belépési és kilépési akciók értéke abban rejlik, hogy garantált inicializálási és takarítási lehetőségeket biztosítanak, nagyon hasonlóan az objektumorientált programozásban használt osztálykonstruktorokhoz és destruktorokhoz. Például vegyük figyelembe az 5. ábrán látható "ajtó_nyitva" állapotot, amely a kenyérpirító sütő viselkedésének felel meg, amikor az ajtó nyitva van. Ez az állapot egy nagyon fontos, biztonságkritikus követelményt tartalmaz: mindig kapcsolja ki a fűtőberendezést, amikor az ajtó nyitva van. Emellett, amíg az ajtó nyitva van, a sütőt megvilágító belső lámpának fel kell kapcsolnia.
Természetesen az ilyen viselkedést úgy is lehet modellezni, hogy megfelelő akciókat (a fűtőberendezés kikapcsolása és a lámpa felkapcsolása) adunk minden átmeneti úthoz, amely a "ajtó_nyitva" állapotba vezet (a felhasználó bármikor kinyithatja az ajtót a "sütés" vagy "pirítás" alatt, vagy amikor a sütő egyáltalán nincs használatban). Nem szabad elfelejteni eloltani a belső lámpát minden "ajtó_nyitva" állapotból kivezető átmenetnél. Azonban egy ilyen megoldás az akciók ismétlődését okozná sok átmenetben. Még fontosabb, hogy ez a megközelítés hibalehetőséget hagy a tervezésben a viselkedés későbbi módosításai során (például a következő programozó, aki egy új funkción, mint például a felső pirítás, dolgozik, egyszerűen elfelejtheti kikapcsolni a fűtőberendezést az "ajtó_nyitva" állapotba történő átmenetkor).
A belépési és kilépési akciók lehetővé teszik a kívánt viselkedés biztonságosabb, egyszerűbb és intuitívabb megvalósítását. Ahogy az 5. ábrán látható, meghatározható, hogy a "fűtés" állapotból való kilépési akció lekapcsolja a fűtőberendezést, a "ajtó_nyitva" állapotba való belépési akció felkapcsolja a sütő lámpáját, és az "ajtó_nyitva" állapotból való kilépési akció eloltja a lámpát. A belépési és kilépési akciók használata előnyösebb, mint az akciók átmenetre helyezése, mivel elkerüli az ismétlődő kódolást és javítja a működést azáltal, hogy kiküszöböli a biztonsági kockázatot (a fűtőberendezés bekapcsolva marad, miközben az ajtó nyitva van). A kilépési akciók szemantikája garantálja, hogy az átmeneti úttól függetlenül a fűtőberendezés lekapcsol, amikor a kenyérpirító nincs a "fűtés" állapotban.
Mivel a belépési akciók automatikusan végrehajtásra kerülnek, amikor a hozzájuk tartozó állapotba belépnek, gyakran meghatározzák a működési feltételeket vagy az állapot identitását, nagyon hasonlóan ahhoz, ahogyan egy osztálykonstruktor meghatározza az éppen létrehozott objektum identitását. Például a "fűtés" állapot identitását az határozza meg, hogy a fűtőberendezés be van kapcsolva. Ezt a feltételt meg kell teremteni, mielőtt a "fűtés" bármely alállapotába belépnénk, mert a "fűtés" alállapotainak, mint például a "pirítás," belépési akciói a "fűtés" szuperállapot megfelelő inicializálására támaszkodnak, és csak az ebből az inicializálásból eredő különbségeket hajtják végre. Ennek következtében a belépési akciók végrehajtási sorrendjének mindig a legkülső állapottól a legbelső állapotig kell haladnia (felülről lefelé).
Nem meglepő módon ez a sorrend analóg azzal a sorrenddel, ahogyan az osztálykonstruktorok meghívásra kerülnek. Egy osztály létrehozása mindig az osztályhierarchia legfelső szintjén kezdődik, és az összes öröklési szinten keresztül halad lefelé az éppen példányosított osztályig. A kilépési akciók végrehajtása, amely megfelel a destruktor meghívásának, pontosan fordított sorrendben történik (alulról felfelé).
Belső átmenetek
[szerkesztés]Nagyon gyakran egy esemény csak néhány belső akció végrehajtását eredményezi, de nem vezet állapotváltozáshoz (állapotátmenethez). Ebben az esetben az összes végrehajtott akció a belső átmenetet képezi. Például, amikor valaki gépel a billentyűzeten, az különböző karakterkódokat generál. Azonban, hacsak nem nyomják meg a Caps Lock billentyűt, a billentyűzet állapota nem változik (nem történik állapotátmenet). Az UML-ben ezt a helyzetet belső átmenetekkel kell modellezni, ahogyan az a 6. ábrán látható. Az UML-jelölésmód a belső átmenetekhez követi a kilépési (vagy belépési) akciók általános szintaxisát, kivéve, hogy a belépés (vagy kilépés) szó helyett a belső átmenet a kiváltó eseménnyel van címkézve (például lásd a 6. ábrán az ANY_KEY esemény által kiváltott belső átmenetet).
Belépési és kilépési akciók hiányában a belső átmenetek azonosak lennének az önátmenetekkel (az átmenetekkel, amelyekben a célállapot megegyezik a forrásállapottal). Valójában egy klasszikus Mealy-gépben az akciók kizárólag az állapotátmenetekhez kapcsolódnak, így az egyetlen módja az akciók végrehajtásának állapotváltozás nélkül egy önátmenet (az 1. ábrán felülről irányított hurokként ábrázolva). Azonban belépési és kilépési akciók jelenlétében, mint az UML-állapotdiagramokban, egy önátmenet magában foglalja a kilépési és belépési akciók végrehajtását, ezért jelentősen különbözik egy belső átmenettől.
Ellentétben az önátmenettel, belépési vagy kilépési akciók soha nem kerülnek végrehajtásra egy belső átmenet eredményeként, még akkor sem, ha a belső átmenet magasabb szintű hierarchiából öröklődik, mint az aktuálisan aktív állapot. A bármely szinten lévő szuperállapotokból örökölt belső átmenetek úgy működnek, mintha közvetlenül az aktuálisan aktív állapotban lennének meghatározva.
Átmeneti végrehajtási sorrend
[szerkesztés]Az állapotok beágyazása, kombinálva a belépési és kilépési akciókkal, jelentősen bonyolítja az állapotátmeneti szemantikát a hierarchikus állapotgépekben (HSM-ek) a hagyományos véges állapotgépekkel (FSM-ek) összehasonlítva. Hierarchikusan beágyazott állapotokkal és ortogonális régiókkal foglalkozva az egyszerű "aktuális állapot" kifejezés meglehetősen zavaró lehet. Egy HSM-ben egyszerre több állapot is aktív lehet. Ha az állapotgép egy levélállapotban van, amely egy összetett állapotban van (amely esetleg egy magasabb szintű összetett állapotban található, és így tovább), akkor minden összetett állapot, amely közvetlenül vagy tranzitívan tartalmazza a levélállapotot, szintén aktív. Továbbá, mivel ennek a hierarchiának egyes összetett állapotai ortogonális régiókkal rendelkezhetnek, az aktuális aktív állapot valójában egy állapotfával van reprezentálva, amely a gyökérnél lévő egyetlen felső állapottól az egyes egyszerű állapotokig terjed a leveleken. Az UML specifikáció az ilyen állapotfára állapotkonfigurációként hivatkozik.[1]
Az UML-ben egy állapotátmenet közvetlenül összekapcsolhat bármely két állapotot. Ezek a két állapot, amelyek lehetnek összetettek is, az átmenet fő forrásának és fő céljának nevezhetők. A 7. ábra egy egyszerű átmeneti példát mutat be, és magyarázza az állapotok szerepét abban az átmenetben. Az UML specifikáció előírja, hogy egy állapotátmenet végrehajtása az alábbi akciók következő sorrendben történő végrehajtását vonja maga után (lásd az OMG Unified Modeling Language (OMG UML), Infrastrukturális Verzió 2.2, 15.3.14 szakaszát:[1]
- Értékelje ki az átmenethez kapcsolódó őrfeltételt, és csak akkor hajtsa végre a következő lépéseket, ha az őrfeltétel értékelése TRUE(Igaz).
- Lépjen ki a forrás állapot konfigurációjából.
- Hajtsa végre az átmenethez kapcsolódó akciókat.
- Lépjen be a cél állapot konfigurációjába.
Az átmenet sorrendje könnyen értelmezhető abban az egyszerű esetben, amikor a fő forrás- és célállapot ugyanazon a szinten van beágyazva. Például, a 7. ábrán látható T1 átmenet az őr g() értékelését okozza, majd a következő akciók sorrendjét: a(); b(); t(); c(); d(); és e(); feltételezve, hogy az őr g() értékelése TRUE.
Azonban, amikor a forrás- és célállapotok különböző szinteken vannak beágyazva az állapothierarchiában, nem mindig nyilvánvaló, hogy hány szintet kell kilépni. Az UML specifikáció[1] előírja, hogy egy átmenet magában foglalja az összes beágyazott állapotból való kilépést a jelenlegi aktív állapottól (amely lehet a fő forrásállapot közvetlen vagy tranzitív alállapota) egészen a legkisebb közös ősig (LCA), de azt nem beleértve, amely a fő forrás- és célállapotok legkisebb közös őse. Ahogy a név is jelzi, az LCA az a legalacsonyabb összetett állapot, amely egyidejűleg a forrás- és célállapotok szuperállapota (őse). Ahogyan korábban leírtuk, a kilépési akciók végrehajtásának sorrendje mindig a legmélyebben beágyazott állapottól (a jelenlegi aktív állapottól) felfelé a hierarchiában az LCA-ig tart, de az LCA-t nem lépve ki. Például, az "s1" és "s2" állapotok LCA-ja a 7. ábrán az "s" állapot.
A célállapot konfigurációjának belépése attól a szinttől kezdődik, ahol a kilépési akciók befejeződtek (azaz az LCA belsejéből). Ahogyan korábban leírtuk, a belépési akciókat a legmagasabb szintű állapottól kezdve lefelé a hierarchiában a fő célállapotig kell végrehajtani. Ha a fő célállapot összetett, az UML szemantika előírja, hogy rekurzívan „fúrjunk” be annak alállapotgépébe a lokális kezdeti átmenetek segítségével. A célállapot konfigurációjába csak akkor lépünk teljesen be, amikor egy olyan levélállapottal találkozunk, amelynek nincsenek kezdeti átmenetei.
Helyi és külső átmenetek
[szerkesztés]Az UML 2 előtt[1] az egyetlen használatban lévő átmenetszemantika a külső átmenet volt, amelyben mindig kilép az átmenet fő forrásából, és mindig megadja az átmenet fő célját. Az UML 2 megőrizte a „külső átmenet”-szemantikát a visszafelé kompatibilitás érdekében, de bevezetett egy újfajta átmenetet is, amelyet helyi átmenetnek neveznek (lásd az Unified Modeling Language (UML) 15.3.15. szakaszát, Infrastructure Version 2.2[1]). Sok átmeneti topológia esetében a külső és a helyi átmenetek valójában azonosak. A helyi átmenet azonban nem okoz kilépést és visszalépést a fő forrásállapotba, ha a fő célállapot a fő forrás alállapota. Ezenkívül a helyi állapotátmenet nem okoz kilépést és visszalépést a fő célállapotból, ha a fő cél a fő forrásállapot szuperállapota.
A 8. ábra a lokális (a) és az externális (b) átmeneteket hasonlítja össze. A felső sorban látható a fő forrást tartalmazó fő célállapot esete. A lokális átmenet nem okoz kilépést a forrásból, míg az externális átmenet kilépést és visszalépést okoz a forrásba. Az alsó sorban a 8. ábrán látható a fő célállapot, amely tartalmazza a fő forrást. A lokális átmenet nem okoz belépést a célállapotba, míg az externális átmenet kilépést és visszalépést okoz a célállapotba.
Események halasztása
[szerkesztés]Néha egy esemény különösen alkalmatlan időpontban érkezik, amikor az állapotgép olyan állapotban van, amely nem tudja kezelni az eseményt. Sok esetben az esemény természete olyan, hogy elhalasztható (bizonyos korlátok között), amíg a rendszer egy másik állapotba nem lép, amelyben jobban fel van készülve az eredeti esemény kezelésére.
Az UML-állapotgépek egy speciális mechanizmust biztosítanak az események halasztására az állapotokban. Minden állapotban szerepelhet egy [eseménylista]/halasztás klauzula. Ha egy esemény bekövetkezik az aktuális állapot halasztott eseménylistájában, az eseményt elmentik (halasztják) későbbi feldolgozásra, amíg egy olyan állapotba nem lépünk, amely nem tartalmazza az eseményt a halasztott eseménylistájában. Ilyen állapotba lépéskor az UML-állapotgép automatikusan felidézi az összes elmentett eseményt, amely már nincs halasztva, majd vagy feldolgozza, vagy elveti ezeket az eseményeket. Lehetséges, hogy egy szuperállapotnak van egy eseményre meghatározott átmenete, amelyet egy alállapot halaszt. Az UML-állapotgépek specifikációjának más területeivel összhangban az alállapot elsőbbséget élvez a szuperállapottal szemben, az esemény halasztásra kerül, és a szuperállapot átmenete nem kerül végrehajtásra. Az ortogonális régiók esetében, ahol az egyik ortogonális régió halaszt egy eseményt, míg egy másik feldolgozza az eseményt, a feldolgozó régió élvez elsőbbséget, és az eseményt feldolgozzák, nem halasztják el.
Az UML-állapotgépek korlátai
[szerkesztés]A Harel állapotdiagramokat, amelyek az UML-állapotgépek előfutárai, "vizuális formalizmusnak a komplex rendszerek számára" találták fel,[12] így már a kezdetektől elválaszthatatlanul kapcsolódtak az állapotdiagramok formájában történő grafikus ábrázoláshoz. Azonban fontos megérteni, hogy az UML-állapotgép koncepciója túlmutat bármilyen konkrét jelölésmódon, legyen az grafikus vagy szöveges. Az UML specifikáció[1] ezt a különbségtételt egyértelművé teszi azáltal, hogy világosan elválasztja az állapotgépek szemantikáját a jelölésmódtól.
Azonban az UML-állapotdiagramok jelölésmódja nem csupán vizuális. Bármely nem triviális állapotgép nagy mennyiségű szöveges információt igényel (például az akciók és őrök specifikációját). Az akciók és őr kifejezések pontos szintaxisa nincs meghatározva az UML specifikációban, így sokan vagy strukturált angolt, vagy formálisabban, egy implementációs nyelv, mint például a C, C++ vagy Java[13] kifejezéseit használják. Gyakorlatban ez azt jelenti, hogy az UML-állapotdiagram jelölésmódja nagymértékben függ az adott programozási nyelvtől.
Mindazonáltal az állapotdiagramok szemantikája nagymértékben a grafikus jelölés felé hajlik. Például az állapotdiagramok gyengén ábrázolják a feldolgozás sorrendjét, legyen az az őrök (guards) értékelési sorrendje vagy az események diszpécselésének sorrendje az ortogonális régiókba. Az UML specifikáció kikerüli ezeket a problémákat azzal, hogy a tervezőre hárítja a felelősséget, hogy ne támaszkodjon semmilyen konkrét sorrendre. Azonban amikor az UML-állapotgépeket ténylegesen megvalósítják, elkerülhetetlenül teljes ellenőrzés van a végrehajtás sorrendje felett, ami kritikához vezet, miszerint az UML szemantikája szükségtelenül korlátozó lehet. Hasonlóképpen, az állapotdiagramok sok „vezetékezési” elemet igényelnek (álállapotok, mint például csatlakozások, elágazások, csomópontok, választási pontok stb.) a vezérlési folyamat grafikus ábrázolásához. Más szóval, ezek a grafikus jelölés elemek nem sok értéket adnak a vezérlési folyamat ábrázolásában a hagyományos strukturált kódhoz képest.
Az UML-jelölésmód és szemantika valójában a számítógépes UML eszközökre van optimalizálva. Az UML-állapotgép, ahogy egy eszközben ábrázolják, nem csupán az állapotdiagram, hanem egy grafikus és szöveges ábrázolás keveréke, amely pontosan rögzíti mind az állapotok topológiáját, mind az akciókat. Az eszköz felhasználói több kiegészítő nézetet is kaphatnak ugyanarról az állapotgépről, vizuális és szöveges formában egyaránt, míg a generált kód csupán egy a sok elérhető nézet közül.
Jegyzetek
[szerkesztés]- ↑ a b c d e f g h i j OMG: OMG Unified Modeling Language (OMG UML), Superstructure Version 2.2, 2009. február 1.
- ↑ "Statecharts: A Visual Formalism for Complex Systems"
- ↑ D. Drusinsky, Modelling and verification using UML statecharts, Elsevier, 2006
- ↑ Samek, Miro: A crash course in UML state machines, 2009. március 1.
- ↑ Samek, Miro. Practical UML Statecharts in C/C++, Second Edition: Event-Driven Programming for Embedded Systems. Newnes, 728. o. (2008). ISBN 978-0-7506-8706-5
- ↑ a b Samek, Miro: Who Moved My State?. C/C++ Users Journal, The Embedded Angle column, 2003. április 1.Samek, Miro (April 2003). "Who Moved My State?". C/C++ Users Journal, The Embedded Angle column.
- ↑ Selic, Bran. Real-Time Object-Oriented Modeling. John Wiley & Sons, 525. o. (1994). ISBN 0-471-59917-4
- ↑ Samek, Miro: Back to Basics. C/C++ Users Journal, The Embedded Angle column, 2003. augusztus 1.
- ↑ Samek: Dj Vu. C/C++ Users Journal, The Embedded Angle column, 2003. június 1. [2012. szeptember 30-i dátummal az eredetiből archiválva].
- ↑ Harel, David. Modeling Reactive Systems with Statecharts, the STATEMATE Approach. McGraw-Hill, 258. o. (1998). ISBN 0-07-026205-5
- ↑ Douglass, Bruce Powel. Doing Hard Time: Developing Real-Time Systems with UML, Objects, Frameworks, and Patterns. Addison Wesley, 749. o. (1999). ISBN 0-201-49837-5
- ↑ Harel, David: Statecharts: A Visual Formalism for Complex Systems, 1987
- ↑ Douglass, Bruce Powel: UML Statecharts. Embedded Systems Programming, 1999. január 1.
Fordítás
[szerkesztés]Ez a szócikk részben vagy egészben az UML state machine című angol Wikipédia-szócikk ezen változatának fordításán alapul. Az eredeti cikk szerkesztőit annak laptörténete sorolja fel. Ez a jelzés csupán a megfogalmazás eredetét és a szerzői jogokat jelzi, nem szolgál a cikkben szereplő információk forrásmegjelöléseként.