Maszyna stanów UML - UML state machine

Maszyna stanów UML , znana również jako UML statechart , jest rozszerzeniem matematycznej koncepcji automatu skończonego w aplikacjach informatycznych , wyrażonej w notacji Unified Modeling Language (UML).

Koncepcje, które za tym stoją, dotyczą organizacji sposobu działania urządzenia, programu komputerowego lub innego (często technicznego) procesu w taki sposób, aby jednostka lub każdy z jej pod-podmiotów zawsze znajdował się dokładnie w jednym z wielu możliwych stanów i tam, gdzie jest dobrze -definiowane warunkowe przejścia między tymi stanami.

Maszyna stanów UML jest opartym na obiektach wariantem schematu stanu Harela , dostosowanym i rozszerzonym przez UML. , Celem maszyn stanowych UML jest przezwyciężenie głównych ograniczeń tradycyjnych maszyn skończonych przy jednoczesnym zachowaniu ich głównych zalet. Karty stanu UML wprowadzają nowe koncepcje hierarchicznie zagnieżdżonych stanów i regionów ortogonalnych , jednocześnie rozszerzając pojęcie akcji . Maszyny stanu UML mają cechy zarówno maszyn Mealy, jak i maszyn Moore'a . Obsługują działania, które zależą zarówno od stanu systemu, jak i zdarzenia wyzwalającego , jak w maszynach Mealy, a także działania wejścia i wyjścia , które są powiązane ze stanami, a nie przejściami, jak w maszynach Moore'a.

Termin „maszyna stanów UML” może odnosić się do dwóch rodzajów maszyn stanów: behawioralnych maszyn stanów i maszyn stanów protokołów . Behawioralne maszyny stanu mogą być używane do modelowania zachowania pojedynczych jednostek (np. Instancji klas), podsystemu, pakietu lub nawet całego systemu. Maszyny stanu protokołów są używane do wyrażania protokołów użycia i mogą służyć do określania scenariuszy legalnego użycia klasyfikatorów, interfejsów i portów.

Podstawowe koncepcje automatu stanowego

Wiele systemów oprogramowania jest sterowanych zdarzeniami , co oznacza, że ​​stale czekają na wystąpienie jakiegoś zdarzenia zewnętrznego lub wewnętrznego , takiego jak kliknięcie myszą, naciśnięcie przycisku, znacznik czasu lub nadejście pakietu danych. Po rozpoznaniu zdarzenia takie systemy reagują wykonując odpowiednie obliczenia, które mogą obejmować manipulowanie sprzętem lub generowanie „miękkich” zdarzeń, które wyzwalają inne wewnętrzne składniki oprogramowania. (Dlatego systemy sterowane zdarzeniami są alternatywnie nazywane systemami reaktywnymi ). Po zakończeniu obsługi zdarzenia system wraca do oczekiwania na następne zdarzenie.

Odpowiedź na zdarzenie zasadniczo zależy zarówno od typu zdarzenia, jak i od stanu wewnętrznego systemu i może obejmować zmianę stanu prowadzącą do zmiany stanu . Wzorzec zdarzeń, stanów i przejść między tymi stanami można wyodrębnić i przedstawić jako maszynę o skończonych stanach (FSM).

Koncepcja FSM jest ważna w programowaniu sterowanym zdarzeniami, ponieważ wyraźnie uzależnia obsługę zdarzeń zarówno od typu zdarzenia, jak i od stanu systemu. Przy prawidłowym użyciu automat stanowy może drastycznie zmniejszyć liczbę ścieżek wykonawczych w kodzie, uprościć warunki testowane w każdym punkcie rozgałęzienia i uprościć przełączanie między różnymi trybami wykonywania. I odwrotnie, używanie programowania sterowanego zdarzeniami bez bazowego modelu FSM może prowadzić programistów do tworzenia podatnego na błędy, trudnego do rozszerzenia i nadmiernie złożonego kodu aplikacji.

Podstawowe diagramy stanów UML

UML zachowuje ogólną postać tradycyjnych diagramów stanu . Diagramy stanów UML są grafami skierowanymi, w których węzły oznaczają stany, a łączniki oznaczają przejścia stanów. Na przykład Rysunek 1 przedstawia diagram stanów UML odpowiadający maszynie stanu klawiatury komputera. W UML stany są reprezentowane jako zaokrąglone prostokąty oznaczone nazwami stanów. Przejścia, reprezentowane jako strzałki, są oznaczone zdarzeniami wyzwalającymi, po których opcjonalnie następuje lista wykonanych akcji. W początkowych przejściowy pochodzi ze stałej okręgu i określa stan domyślny, gdy system najpierw zaczyna. Każdy diagram stanów powinien mieć takie przejście, które nie powinno być etykietowane, ponieważ nie jest wyzwalane przez zdarzenie. Początkowe przejście może mieć powiązane akcje.

Rysunek 1: Diagram stanów UML przedstawiający maszynę stanu klawiatury komputera

Wydarzenia

Wydarzenie jest czymś, co zdarza się, że wpływa na układ. Ściśle mówiąc, w specyfikacji UML termin zdarzenie odnosi się raczej do typu zdarzenia niż do jakiegokolwiek konkretnego wystąpienia tego zdarzenia. Na przykład naciśnięcie klawisza jest zdarzeniem dla klawiatury, ale każde naciśnięcie klawisza nie jest zdarzeniem, ale konkretną instancją zdarzenia naciśnięcia klawisza. Innym interesującym wydarzeniem dla klawiatury może być włączenie zasilania, ale włączenie zasilania jutro o 10:05:36 będzie tylko przykładem zdarzenia włączenia zasilania.

Ze zdarzeniem mogą być powiązane parametry , dzięki czemu zdarzenie może przekazać nie tylko wystąpienie jakiegoś interesującego incydentu, ale także informacje ilościowe dotyczące tego zdarzenia. Na przykład zdarzenie Keystroke generowane przez naciśnięcie klawisza na klawiaturze komputera ma powiązane parametry, które przekazują kod skanowania znaków, a także stan klawiszy Shift, Ctrl i Alt.

Instancja zdarzenia przeżywa chwilowe wystąpienie, które ją wygenerowało i może przekazać to wystąpienie do jednego lub kilku automatów stanu. Po wygenerowaniu instancja zdarzenia przechodzi przez cykl życia przetwarzania, który może składać się z maksymalnie trzech etapów. Po pierwsze, instancja zdarzenia jest odbierana, gdy zostanie zaakceptowana i oczekuje na przetworzenie (np. Zostanie umieszczona w kolejce zdarzeń ). Później instancja zdarzenia jest wysyłana do automatu stanowego, w którym to momencie staje się zdarzeniem bieżącym. Na koniec jest używany, gdy automat stanowy zakończy przetwarzanie wystąpienia zdarzenia. Zużywana instancja zdarzenia nie jest już dostępna do przetwarzania.

Stany

Każdy automat stanowy ma stan , który reguluje reakcją automatu stanowego na zdarzenia. Na przykład, gdy uderzysz w klawisz na klawiaturze, wygenerowany kod znaku będzie wielką lub małą literą, w zależności od tego, czy aktywna jest funkcja Caps Lock. Dlatego zachowanie klawiatury można podzielić na dwa stany: stan „default” i stan „caps_locked”. (Większość klawiatur zawiera diodę LED, która wskazuje, że klawiatura jest w stanie „Caps_locked”). Zachowanie klawiatury zależy tylko od pewnych aspektów jej historii, mianowicie od tego, czy został naciśnięty klawisz Caps Lock, ale nie np. o ile i dokładnie jakie inne klawisze zostały wcześniej naciśnięte. Stan może wyodrębnić wszystkie możliwe (ale nieistotne) sekwencje zdarzeń i uchwycić tylko te istotne.

W kontekście automatów stanu oprogramowania (a zwłaszcza klasycznych FSM), termin stan jest często rozumiany jako pojedyncza zmienna stanu, która może przyjmować tylko ograniczoną liczbę wartości określonych a priori (np. Dwie wartości w przypadku klawiatury lub więcej ogólnie - jakiś rodzaj zmiennej z typem wyliczenia w wielu językach programowania). Idea zmiennej stanu (i klasycznego modelu FSM) polega na tym, że wartość zmiennej stanu w pełni definiuje bieżący stan systemu w danym momencie. Koncepcja stanu ogranicza problem identyfikacji kontekstu wykonania w kodzie do testowania tylko zmiennej stanu zamiast wielu zmiennych, eliminując w ten sposób wiele logiki warunkowej.

Stany rozszerzone

W praktyce jednak interpretowanie całego stanu automatu stanowego jako pojedynczej zmiennej stanu szybko staje się niepraktyczne dla wszystkich automatów stanowych poza bardzo prostymi. Rzeczywiście, nawet jeśli mamy jedną 32-bitową liczbę całkowitą w stanie naszej maszyny, może ona przyczynić się do powstania ponad 4 miliardów różnych stanów - i doprowadzi do przedwczesnej eksplozji stanu . Ta interpretacja nie jest praktyczna, więc w automatach stanu UML cały stan automatu stanowego jest zwykle dzielony na (a) wyliczalną zmienną stanu i (b) wszystkie inne zmienne, które nazywane są stanem rozszerzonym . Innym sposobem postrzegania tego jest interpretacja wyliczalnych zmiennych stanu jako aspektu jakościowego, a stan rozszerzony jako ilościowych aspektów całego stanu. W tej interpretacji zmiana zmiennej nie zawsze oznacza zmianę jakościowych aspektów zachowania systemu, a zatem nie prowadzi do zmiany stanu.

Maszyny stanu uzupełnione o rozszerzone zmienne stanu nazywane są maszynami stanu rozszerzonego, a maszyny stanu UML należą do tej kategorii. Maszyny o rozszerzonych stanach mogą zastosować podstawowy formalizm do znacznie bardziej złożonych problemów niż jest to praktyczne bez uwzględnienia rozszerzonych zmiennych stanu. Na przykład, gdybyśmy musieli zaimplementować jakiś rodzaj ograniczenia w naszym FSM (powiedzmy ograniczenie liczby naciśnięć klawiszy na klawiaturze do 1000), bez stanu rozszerzonego musielibyśmy stworzyć i przetworzyć 1000 stanów - co nie jest praktyczne; jednakże w przypadku rozszerzonego automatu stanowego możemy wprowadzić key_count zmienną, która jest inicjalizowana na 1000 i zmniejszana po każdym naciśnięciu klawisza bez zmiany zmiennej stanu .

Rysunek 2: Rozszerzony automat stanów „taniej klawiatury” z rozszerzoną zmienną stanu key_count i różnymi warunkami ochrony

Diagram stanu z rysunku 2 jest przykładem rozszerzonej maszyny stanu, w której całkowity stan systemu (zwany stanem rozszerzonym ) jest połączeniem aspektu jakościowego - zmiennej stanu - i aspektów ilościowych - rozszerzonych zmiennych stanu .

Oczywistą zaletą maszyn o rozszerzonych stanach jest elastyczność. Na przykład zmiana limitu zarządzanego przez key_count klawisze z 1000 na 10000 nie skomplikowałaby w ogóle rozszerzonej maszyny stanowej. Jedyną wymaganą modyfikacją byłaby zmiana wartości inicjalizacji key_count rozszerzonej zmiennej stanu podczas inicjalizacji.

Ta elastyczność maszyn o rozszerzonym stanie ma jednak swoją cenę ze względu na złożone powiązanie między „jakościowymi” i „ilościowymi” aspektami stanu rozszerzonego. Sprzężenie zachodzi poprzez warunki ochronne dołączone do przejść, jak pokazano na rysunku 2.

Warunki straży

Warunki ochronne (lub po prostu strażnicy) to wyrażenia logiczne obliczane dynamicznie na podstawie wartości rozszerzonych zmiennych stanu i parametrów zdarzenia . Warunki ochronne wpływają na zachowanie automatu stanowego, włączając akcje lub przejścia tylko wtedy, gdy mają wartość PRAWDA i wyłączając je, gdy oceniają jako FAŁSZ. W notacji UML warunki ochronne są pokazane w nawiasach kwadratowych (np. Na [key_count == 0] rysunku 2).

Potrzeba strażników jest bezpośrednią konsekwencją dodania rozszerzonych zmiennych stanu pamięci do formalizmu automatu stanowego. Używane oszczędnie, rozszerzone zmienne stanu i osłony tworzą potężny mechanizm, który może uprościć projekty. Z drugiej strony można dość łatwo nadużywać rozszerzonych stanów i strażników.

Działania i przejścia

Gdy uruchamiana jest instancja zdarzenia , automat stanu odpowiada wykonując czynności , takie jak zmiana zmiennej, wykonanie operacji we / wy, wywołanie funkcji, wygenerowanie innej instancji zdarzenia lub przejście do innego stanu. Wszelkie wartości parametrów skojarzone z bieżącym zdarzeniem są dostępne dla wszystkich akcji bezpośrednio spowodowanych przez to zdarzenie.

Przełączanie się z jednego stanu do drugiego nazywa się przejściem stanu , a zdarzenie, które je wywołuje, nazywane jest zdarzeniem wyzwalającym lub po prostu wyzwalaczem . W przykładzie z klawiaturą, jeśli klawiatura jest w stanie „domyślnym” po naciśnięciu klawisza CapsLock, klawiatura przejdzie w stan „caps_locked”. Jeśli jednak klawiatura jest już w stanie „caps_locked”, naciśnięcie CapsLock spowoduje inne przejście - ze stanu „caps_locked” do stanu „default”. W obu przypadkach naciśnięcie klawisza CapsLock jest zdarzeniem wyzwalającym.

W automatach z rozszerzonym stanem przejście może mieć strażnika , co oznacza, że ​​przejście może „odpalić” tylko wtedy, gdy wartownik uzyska wartość TRUE. Stan może mieć wiele przejść w odpowiedzi na ten sam wyzwalacz, o ile mają one nie nakładające się osłony; Jednak sytuacja ta może powodować problemy w kolejności oceny strażników, gdy wystąpi wspólny wyzwalacz. Specyfikacja UML celowo nie określa żadnej konkretnej kolejności; UML nakłada raczej na projektanta ciężar zaprojektowania osłon w taki sposób, aby kolejność ich oceny nie miała znaczenia. W praktyce oznacza to, że wyrażenia strażnicze nie powinny mieć żadnych skutków ubocznych, a przynajmniej żadnych, które zmieniłyby ocenę innych strażników z tym samym wyzwalaczem.

Model wykonywania od początku do końca

Wszystkie formalizmy automatu stanowego, w tym automaty stanów UML, ogólnie zakładają, że maszyna stanów kończy przetwarzanie każdego zdarzenia, zanim będzie mogła rozpocząć przetwarzanie następnego zdarzenia. Ten model wykonania nosi nazwę run to complete lub RTC.

W modelu RTC system przetwarza zdarzenia w dyskretnych, niepodzielnych krokach RTC. Nowe zdarzenia przychodzące nie mogą przerywać przetwarzania bieżącego zdarzenia i muszą być przechowywane (zazwyczaj w kolejce zdarzeń ), dopóki automat stanowy nie stanie się ponownie bezczynny. Ta semantyka całkowicie pozwala uniknąć wszelkich wewnętrznych problemów ze współbieżnością w ramach jednego automatu stanowego. Model RTC omija również konceptualny problem działań przetwarzania związanych z przejściami, w których automat stanowy nie jest w dobrze zdefiniowanym stanie (znajduje się między dwoma stanami) przez czas trwania akcji. Podczas przetwarzania zdarzeń system nie reaguje (nieobserwowalny), więc źle zdefiniowany stan w tym czasie nie ma praktycznego znaczenia.

Należy jednak pamiętać, że RTC nie oznacza, że ​​maszyna stanowa musi zmonopolizować procesor do czasu zakończenia kroku RTC. Ograniczenie wywłaszczania dotyczy tylko kontekstu zadania maszyny stanowej, która jest już zajęta przetwarzaniem zdarzeń. W środowisku wielozadaniowym mogą być uruchomione inne zadania (niezwiązane z kontekstem zadań zajętej maszyny stanowej), prawdopodobnie wywłaszczające aktualnie wykonywaną maszynę stanów. Dopóki inne maszyny stanowe nie współużytkują ze sobą zmiennych ani innych zasobów, nie ma zagrożeń związanych ze współbieżnością .

Główną zaletą przetwarzania RTC jest prostota. Jego największą wadą jest to, że czas reakcji automatu stanowego jest określany przez jej najdłuższy krok RTC. Osiąganie krótkich kroków RTC może często znacznie komplikować projekty w czasie rzeczywistym.

Rozszerzenia UML do tradycyjnego formalizmu FSM

Chociaż tradycyjne FSM są doskonałym narzędziem do rozwiązywania mniejszych problemów, powszechnie wiadomo, że stają się one niemożliwe do opanowania, nawet w przypadku średnio zaangażowanych systemów. Ze względu na zjawisko znane jako eksplozja stanu i przejścia , złożoność tradycyjnego FSM ma tendencję do wzrostu znacznie szybciej niż złożoność opisywanego przez niego systemu. Dzieje się tak, ponieważ tradycyjny formalizm machiny państwowej wymusza powtórzenia. Na przykład, jeśli spróbujesz przedstawić zachowanie prostego kalkulatora kieszonkowego z tradycyjnym FSM, od razu zauważysz, że wiele zdarzeń (np. Naciśnięcia przycisku Wyczyść lub Wyłącz) jest obsługiwanych identycznie w wielu stanach. Konwencjonalny FSM pokazany na poniższym rysunku nie ma możliwości uchwycenia takiej wspólności i wymaga powtarzania tych samych działań i przejść w wielu stanach. To, czego brakuje w tradycyjnych automatach stanowych, to mechanizm wyodrębniania typowego zachowania w celu podzielenia go na wiele stanów.

Kalkulator kieszonkowy (po lewej) i tradycyjny automat z wieloma przejściami Clear and Off (po prawej)

Maszyny stanu UML rozwiązują dokładnie tę wadę konwencjonalnych FSM. Zapewniają szereg funkcji eliminujących powtórzenia, dzięki czemu złożoność automatu stanu UML nie eksploduje, ale ma tendencję do wiernego odzwierciedlania złożoności opisywanego systemu reaktywnego. Oczywiście te funkcje są bardzo interesujące dla programistów, ponieważ tylko dzięki nim podejście całego automatu stanowego jest naprawdę przydatne w rzeczywistych problemach.

Stany zagnieżdżone hierarchicznie

Najważniejszą innowacją maszyn stanu UML w porównaniu z tradycyjnymi FSM jest wprowadzenie hierarchicznie zagnieżdżonych stanów (dlatego też karty stanu są również nazywane hierarchicznymi maszynami stanu lub HSM ). Semantyka związana z zagnieżdżaniem stanu jest następująca (patrz rysunek 3): Jeśli system jest w stanie zagnieżdżonym, na przykład „wynik” (nazywany podstanem ), to również (niejawnie) jest w stanie otaczającym „włączony” (nazywany superpaństwem ). Ten automat stanowy będzie próbował obsłużyć każde zdarzenie w kontekście podstanu, który koncepcyjnie znajduje się na niższym poziomie hierarchii. Jeśli jednak podrzędny „wynik” nie określa, jak obsłużyć zdarzenie, zdarzenie nie jest po cichu odrzucane, jak w tradycyjnej „płaskiej” maszynie stanu; jest raczej obsługiwany automatycznie na wyższym poziomie kontekstu superpaństwa „włączonego”. To właśnie oznacza, że ​​system jest w stanie „wynik” oraz „włączony”. Oczywiście zagnieżdżanie stanów nie jest ograniczone tylko do jednego poziomu, a prosta reguła przetwarzania zdarzeń ma zastosowanie rekurencyjnie do każdego poziomu zagnieżdżenia.

Rysunek 3: Kalkulator kieszonkowy (po lewej) i automat stanowy UML z zagnieżdżaniem stanów (po prawej)

Stany, które zawierają inne stany, nazywane są stanami złożonymi ; odwrotnie, stany bez struktury wewnętrznej nazywane są stanami prostymi . Stan zagnieżdżony nazywany jest bezpośrednim podstanem, jeśli nie jest zawarty w żadnym innym stanie; w przeciwnym razie jest określany jako przejściowo zagnieżdżony podstat .

Ponieważ wewnętrzna struktura stanu złożonego może być dowolnie złożona, każdy hierarchiczny automat stanów można postrzegać jako strukturę wewnętrzną jakiegoś (wyższego poziomu) stanu złożonego. Koncepcyjnie wygodne jest zdefiniowanie jednego złożonego stanu jako ostatecznego źródła hierarchii automatu stanowego. W specyfikacji UML każdy automat stanowy ma najwyższy stan (abstrakcyjny katalog główny każdej hierarchii automatu stanowego), który zawiera wszystkie inne elementy całego automatu stanowego. Graficzne renderowanie tego obejmującego wszystko stanu górnego jest opcjonalne.

Jak widać, semantyka hierarchicznej dekompozycji stanu ma na celu ułatwienie ponownego wykorzystania zachowania. Substancje (stany zagnieżdżone) muszą jedynie określać różnice w stosunku do superpaństw (zawierających stany). Podstan może łatwo odziedziczyć powszechne zachowanie po swoim superpaństwie (superpaństwach), po prostu ignorując powszechnie obsługiwane zdarzenia, które są następnie automatycznie obsługiwane przez stany wyższego poziomu. Innymi słowy, hierarchiczne zagnieżdżanie stanów umożliwia programowanie na podstawie różnic .

Najczęściej podkreślanym aspektem hierarchii stanów jest abstrakcja - stara i potężna technika radzenia sobie ze złożonością. Zamiast zajmować się jednocześnie wszystkimi aspektami złożonego systemu, często można zignorować (wyodrębnić) niektóre części systemu. Stany hierarchiczne są idealnym mechanizmem do ukrywania wewnętrznych szczegółów, ponieważ projektant może łatwo oddalać lub powiększać, aby ukryć lub pokazać stany zagnieżdżone.

Jednak stany złożone nie tylko ukrywają złożoność; aktywnie redukują je również dzięki potężnemu mechanizmowi hierarchicznego przetwarzania zdarzeń. Bez takiego ponownego wykorzystania nawet umiarkowany wzrost złożoności systemu mógłby doprowadzić do gwałtownego wzrostu liczby stanów i przejść. Na przykład hierarchiczna maszyna stanów reprezentująca kalkulator kieszonkowy (Rysunek 3) unika powtarzania przejść Clear i Off w praktycznie każdym stanie. Unikanie powtórzeń pozwala na utrzymanie wzrostu HSM proporcjonalnie do wzrostu złożoności systemu. Wraz z rozwojem modelowanego systemu zwiększa się również szansa na jego ponowne wykorzystanie, a tym samym potencjalnie przeciwdziała nieproporcjonalnemu wzrostowi liczby stanów i zmianom typowym dla tradycyjnych FSM.

Regiony ortogonalne

Analiza poprzez hierarchiczną dekompozycję stanu może obejmować zastosowanie operacji „wyłączne-LUB” do dowolnego danego stanu. Na przykład, jeśli system znajduje się w superpaństwie „włączony” (Rysunek 3), może się zdarzyć, że znajduje się on również w podrzędnym „operand1” LUB podrzędnym „operand2” LUB podrzędnym „opEntered” LUB „wyniku” podst. Prowadziłoby to do opisu superpaństwa „on” jako „stanu OR”.

Wykresy stanu UML wprowadzają również komplementarną dekompozycję AND. Taka dekompozycja oznacza, że ​​stan złożony może zawierać dwa lub więcej regionów ortogonalnych (środki ortogonalne kompatybilne i niezależne w tym kontekście), a bycie w takim stanie złożonym pociąga za sobą jednoczesne przebywanie we wszystkich jego regionach ortogonalnych.

Regiony ortogonalne rozwiązują częsty problem kombinatorycznego wzrostu liczby stanów, gdy zachowanie systemu jest podzielone na niezależne, współbieżnie aktywne części. Na przykład, oprócz głównej klawiatury, klawiatura komputera posiada niezależną klawiaturę numeryczną. Z poprzedniej dyskusji przypomnij sobie dwa już zidentyfikowane stany głównej klawiatury: „default” i „caps_locked” (patrz Rysunek 1). Klawiatura numeryczna może być również w dwóch stanach - „cyfry” i „strzałki” - w zależności od tego, czy klawisz Num Lock jest aktywny. Pełna przestrzeń stanów klawiatury w standardowej dekompozycji jest zatem iloczynem kartezjańskim dwóch komponentów (głównej klawiatury i klawiatury numerycznej) i składa się z czterech stanów: „domyślne – numery”, „domyślne – strzałki,” „caps_locked – numbers, „i„ caps_locked – arrows ”. Byłoby to jednak nienaturalne odwzorowanie, ponieważ zachowanie klawiatury numerycznej nie zależy od stanu klawiatury głównej i odwrotnie. Użycie regionów ortogonalnych pozwala uniknąć mieszania się niezależnych zachowań jako produktu kartezjańskiego, a zamiast tego zachować je oddzielnie, jak pokazano na rysunku 4.

Rysunek 4: Dwa regiony ortogonalne (główna klawiatura i klawiatura numeryczna) klawiatury komputera

Zauważ, że jeśli regiony ortogonalne są w pełni niezależne od siebie, ich złożoność jest po prostu addytywna, co oznacza, że ​​liczba niezależnych stanów potrzebnych do modelowania systemu jest po prostu sumą k + l + m + ... , gdzie k, l, m, ... oznaczają liczbę stanów OR w każdym regionie ortogonalnym. Jednak ogólny przypadek wzajemnego uzależnienia, z drugiej strony, wyniki w multiplikatywnego złożoności, tak w ogóle, liczba stanów potrzebnych jest iloczynem k × l × m × ... .

W większości rzeczywistych sytuacji regiony ortogonalne byłyby tylko w przybliżeniu ortogonalne (tj. Nie byłyby naprawdę niezależne). W związku z tym wykresy stanu UML zapewniają wiele sposobów komunikowania się i synchronizowania zachowań regionów ortogonalnych. Wśród tych bogatych zestawów (czasem złożonych) mechanizmów, być może najważniejszą cechą jest to, że regiony ortogonalne mogą koordynować swoje zachowania, przesyłając do siebie instancje zdarzeń.

Mimo że regiony ortogonalne implikują niezależność wykonywania (pozwalając na większą lub mniejszą współbieżność), specyfikacja UML nie wymaga przypisania oddzielnego wątku wykonywania do każdego regionu ortogonalnego (chociaż można to zrobić w razie potrzeby). W rzeczywistości najczęściej regiony ortogonalne są wykonywane w tym samym wątku. Specyfikacja UML wymaga jedynie, aby projektant nie polegał na żadnej określonej kolejności wysyłania instancji zdarzeń do odpowiednich regionów ortogonalnych.

Akcje wejścia i wyjścia

Każdy stan w schemacie stanu UML może mieć opcjonalne akcje wejścia , które są wykonywane po wejściu do stanu, jak również opcjonalne akcje wyjścia , które są wykonywane po wyjściu ze stanu. Akcje wejścia i wyjścia są powiązane ze stanami, a nie przejściami. Niezależnie od tego, w jaki sposób stan zostanie wprowadzony lub zamknięty, wszystkie jego akcje wejścia i wyjścia zostaną wykonane. Ze względu na tę cechę wykresy stanu zachowują się jak maszyny Moore'a . Notacja UML dla akcji wejścia i wyjścia stanu polega na umieszczeniu zarezerwowanego słowa „wejście” (lub „wyjście”) w stanie tuż pod przedziałem z nazwą, po którym następuje ukośnik i lista dowolnych działań (patrz Rysunek 5).

Rysunek 5: Stan maszyny z tosterem z akcjami wejścia i wyjścia

Wartość działań wejścia i wyjścia polega na tym, że zapewniają one środki do gwarantowanej inicjalizacji i czyszczenia , podobnie jak konstruktory klas i destruktory w programowaniu obiektowym . Na przykład, weźmy pod uwagę stan „drzwi_otwarte” z rysunku 5, który odpowiada zachowaniu tostera, gdy drzwiczki są otwarte. Ten stan ma bardzo ważny wymóg krytyczny dla bezpieczeństwa: Zawsze wyłączaj grzejnik, gdy drzwi są otwarte. Dodatkowo przy otwartych drzwiczkach powinna się zapalić wewnętrzna lampka oświetlająca piekarnik.

Oczywiście takie zachowanie można by wymodelować dodając odpowiednie akcje (wyłączenie grzałki i włączenie światła) do każdej ścieżki przejścia prowadzącej do stanu "drzwi_otwarte" (użytkownik może w każdej chwili otworzyć drzwiczki podczas "pieczenia" lub "opiekania ”lub gdy piekarnik w ogóle nie jest używany). Nie należy zapominać o wygaszaniu wewnętrznej lampy przy każdym przejściu wychodzącym ze stanu „door_open”. Jednak takie rozwiązanie powodowałoby powtarzanie się czynności w wielu przejściach. Co ważniejsze, takie podejście pozostawia projekt podatny na błędy podczas kolejnych poprawek zachowania (np. Następny programista pracujący nad nową funkcją, taką jak top-browning, może po prostu zapomnieć o wyłączeniu grzejnika przy przejściu do "door_open").

Akcje wejścia i wyjścia pozwalają na implementację pożądanego zachowania w bezpieczniejszy, prostszy i bardziej intuicyjny sposób. Jak pokazano na Rysunku 5, można określić, że operacja wyjścia z „ogrzewania” wyłącza grzejnik, akcja wejścia do „drzwi_otwarte” zapala lampkę piekarnika, a czynność wyjścia z „otwarcia_drzwiczek” gaśnie lampę. Stosowanie działań wejścia i wyjścia jest lepsze niż umieszczenie działania na przejściu, ponieważ pozwala uniknąć powtarzającego się kodowania i poprawia działanie poprzez eliminację zagrożenia bezpieczeństwa; (grzejnik włączony przy otwartych drzwiach). Semantyka akcji wyjściowych gwarantuje, że niezależnie od ścieżki przejścia grzałka zostanie wyłączona, gdy toster nie będzie w stanie „grzanie”.

Ponieważ akcje wejściowe są wykonywane automatycznie po każdym wejściu w skojarzony stan, często określają warunki działania lub tożsamość stanu, podobnie jak konstruktor klasy określa tożsamość konstruowanego obiektu. Na przykład tożsamość stanu „grzania” jest określona przez fakt, że grzejnik jest włączony. Warunek ten musi zostać ustalony przed wejściem do podstanu „ogrzewania”, ponieważ czynności związane z wejściem do podstanu „ogrzewania”, takie jak „opiekanie”, polegają na prawidłowej inicjalizacji superpaństwa „ogrzewania” i wykonują tylko różnice od tej inicjalizacji. W konsekwencji kolejność wykonywania czynności wejściowych musi zawsze przebiegać od stanu najbardziej oddalonego do stanu najbardziej wewnętrznego (odgórnego).

Nic dziwnego, że ta kolejność jest analogiczna do kolejności, w której wywoływane są konstruktory klas. Konstrukcja klasy zawsze zaczyna się w samym korzeniu hierarchii klas i przechodzi przez wszystkie poziomy dziedziczenia, aż do tworzonej instancji klasy. Wykonywanie akcji wyjścia, które odpowiada wywołaniu destruktora, przebiega w dokładnie odwrotnej kolejności (od dołu do góry).

Przejścia wewnętrzne

Bardzo często zdarzenie powoduje wykonanie tylko niektórych akcji wewnętrznych, ale nie prowadzi do zmiany stanu (przejścia stanu). W tym przypadku wszystkie wykonane akcje obejmują przejście wewnętrzne . Na przykład, kiedy ktoś pisze na klawiaturze, odpowiada generując różne kody znaków. Jednak jeśli nie zostanie naciśnięty klawisz Caps Lock, stan klawiatury nie zmienia się (nie następuje żadna zmiana stanu). W UML tę sytuację należy modelować za pomocą przejść wewnętrznych, jak pokazano na rysunku 6. Notacja UML dla przejść wewnętrznych jest zgodna z ogólną składnią używaną do akcji wyjścia (lub wejścia), z wyjątkiem zamiast słowa wejście (lub wyjście) przejścia wewnętrznego jest oznaczony zdarzeniem wyzwalającym (np. zobacz wewnętrzne przejście wyzwalane przez zdarzenie ANY_KEY na Rysunku 6).

Rysunek 6: Diagram stanów UML maszyny stanu klawiatury z przejściami wewnętrznymi

W przypadku braku działań wejścia i wyjścia, przejścia wewnętrzne byłyby identyczne jak przejścia własne (przejścia, w których stan docelowy jest taki sam jak stan źródłowy). W rzeczywistości w klasycznej maszynie Mealy'ego akcje są powiązane wyłącznie z przejściami stanów, więc jedynym sposobem wykonania akcji bez zmiany stanu jest przejście automatyczne (pokazane jako skierowana pętla na rysunku 1 od początku tego artykułu). Jednak w przypadku obecności akcji wejścia i wyjścia, tak jak w przypadku wykresów stanu UML, przejście własne obejmuje wykonanie działań wyjścia i wejścia, a zatem różni się wyraźnie od przejścia wewnętrznego.

W przeciwieństwie do przejścia samodzielnego, żadne akcje wejścia ani wyjścia nie są nigdy wykonywane w wyniku przejścia wewnętrznego, nawet jeśli przejście wewnętrzne jest dziedziczone z wyższego poziomu hierarchii niż stan aktualnie aktywny. Przejścia wewnętrzne odziedziczone po superpaństwach na dowolnym poziomie zagnieżdżenia działają tak, jakby były zdefiniowane bezpośrednio w aktualnie aktywnym stanie.

Sekwencja wykonania przejścia

Zagnieżdżanie stanów w połączeniu z akcjami wejścia i wyjścia znacznie komplikuje semantykę przejścia między stanami w modułach HSM w porównaniu z tradycyjnymi modułami FSM. W przypadku stanów zagnieżdżonych hierarchicznie i regionów ortogonalnych prosty termin stan bieżący może być dość mylący. W HSM może być jednocześnie aktywny więcej niż jeden stan. Jeśli maszyna stanu jest w stanie liścia, który jest zawarty w stanie złożonym (który prawdopodobnie jest zawarty w stanie złożonym wyższego poziomu itd.), Wszystkie stany złożone, które zawierają bezpośrednio lub przejściowo stan liścia, są również aktywne . Ponadto, ponieważ niektóre stany złożone w tej hierarchii mogą mieć regiony ortogonalne, bieżący stan aktywny jest w rzeczywistości reprezentowany przez drzewo stanów, począwszy od pojedynczego stanu u góry u podstawy, aż do pojedynczych stanów prostych na liściach. Specyfikacja UML odnosi się do takiego drzewa stanów jako konfiguracji stanu.

Rysunek 7: Role stanów w przejściu między stanami

W UML zmiana stanu może bezpośrednio łączyć dowolne dwa stany. Te dwa stany, które mogą być złożone, są określane jako główne źródło i główny cel przejścia. Rysunek 7 przedstawia prosty przykład przejścia i wyjaśnia role stanów w tym przejściu. Specyfikacja UML wymaga, aby przejście stanu wymagało wykonania następujących działań w następującej kolejności (patrz sekcja 15.3.14 w OMG Unified Modeling Language (OMG UML), Infrastructure Version 2.2 ):

  1. Oceń warunek ochrony skojarzony z przejściem i wykonaj następujące kroki tylko wtedy, gdy strażnik ma wartość PRAWDA.
  2. Wyjdź z konfiguracji stanu źródła.
  3. Wykonaj czynności związane z przejściem.
  4. Wprowadź konfigurację stanu docelowego.

Sekwencja przejść jest łatwa do zinterpretowania w prostym przypadku zagnieżdżenia głównego źródła i głównego celu na tym samym poziomie. Na przykład przejście T1 pokazane na rysunku 7 powoduje ocenę osłony g (); po którym następuje sekwencja działań: a(); b(); t(); c(); d(); i e() ; zakładając, że strażnik g() oceni jako PRAWDA.

Jednak w ogólnym przypadku stanów źródłowych i docelowych zagnieżdżonych na różnych poziomach hierarchii stanów może nie być od razu oczywiste, z ilu poziomów zagnieżdżenia należy wyjść. Specyfikacja UML wymaga, aby przejście obejmowało wyjście ze wszystkich zagnieżdżonych stanów z bieżącego stanu aktywnego (który może być bezpośrednim lub przechodnim podstatem głównego stanu źródłowego) do, ale bez uwzględniania, stanu najmniejszego wspólnego przodka (LCA) głównego źródła i główne stany docelowe. Jak nazwa wskazuje, LCA jest najniższym stanem złożonym, który jest jednocześnie superpaństwem (przodkiem) zarówno stanu źródłowego, jak i docelowego. Jak opisano wcześniej, kolejność wykonywania działań wyjścia jest zawsze od stanu najgłębiej zagnieżdżonego (bieżącego stanu aktywnego) w górę w hierarchii do LCA, ale bez wychodzenia z LCA. Na przykład LCA (s1, s2) stanów „s1” i „s2” pokazane na rysunku 7 to stan „s”.

Wchodzenie w konfigurację stanu docelowego rozpoczyna się od poziomu, na którym zakończono działania wyjściowe (tj. Z wnętrza LCA). Jak opisano wcześniej, akcje wejściowe muszą być wykonywane począwszy od stanu najwyższego poziomu w dół w hierarchii stanów do głównego stanu docelowego. Jeśli główny stan docelowy jest złożony, semantyka UML nakazuje „drążenie” w jego podmaszynie rekurencyjnie przy użyciu lokalnych przejść początkowych. Konfiguracja stanu docelowego jest całkowicie wprowadzana dopiero po napotkaniu stanu liścia, który nie ma początkowych przejść.

Przejścia lokalne a zewnętrzne

Przed UML 2 jedyną używaną semantyką przejścia było przejście zewnętrzne , w którym główne źródło przejścia jest zawsze zamykane, a główny cel przejścia jest zawsze wprowadzany. UML 2 zachował semantykę „przejścia zewnętrznego” w celu zapewnienia kompatybilności wstecznej, ale wprowadził również nowy rodzaj przejścia zwanego przejściem lokalnym (patrz sekcja 15.3.15 w Unified Modeling Language (UML), Infrastructure Version 2.2 ). W przypadku wielu topologii przejść przejścia zewnętrzne i lokalne są w rzeczywistości identyczne. Jednak przejście lokalne nie powoduje wyjścia z głównego stanu źródłowego i powrotu do niego, jeśli główny stan docelowy jest podstanem głównego źródła. Ponadto lokalna zmiana stanu nie powoduje wyjścia z głównego stanu docelowego i ponownego wejścia do niego, jeśli głównym celem jest superpaństwo głównego stanu źródłowego.

Rysunek 8: Przejścia lokalne (a) i zewnętrzne (b).

Rysunek 8 porównuje przejścia lokalne (a) i zewnętrzne (b). W górnym rzędzie widać przypadek głównego źródła zawierającego główny cel. Przejście lokalne nie powoduje wyjścia ze źródła, natomiast przejście zewnętrzne powoduje wyjście i powrót do źródła. W dolnym rzędzie na rysunku 8 widać przypadek głównego celu zawierającego główne źródło. Lokalne przejście nie powoduje wejścia do celu, podczas gdy zewnętrzne przejście powoduje wyjście i ponowne wejście do celu.

Odroczenie wydarzenia

Czasami zdarzenie pojawia się w szczególnie niewygodnym czasie, gdy automat stanowy jest w stanie, który nie może obsłużyć zdarzenia. W wielu przypadkach charakter zdarzenia jest taki, że można je odłożyć (w określonych granicach) do czasu, gdy system wejdzie w inny stan, w którym jest lepiej przygotowany do obsługi pierwotnego zdarzenia.

Automaty stanu UML zapewniają specjalny mechanizm odraczania zdarzeń w stanach. W każdym stanie możesz dołączyć klauzulę [event list]/defer . Jeśli wystąpi zdarzenie na liście odroczonych zdarzeń w bieżącym stanie, zostanie ono zapisane (odroczone) do przyszłego przetworzenia do czasu wprowadzenia stanu, który nie umieszcza zdarzenia na liście odroczonych zdarzeń. Po wejściu w taki stan automat stanowy UML automatycznie przywoła każde zapisane zdarzenie (a), które nie są już odroczone, a następnie albo zużyje, albo odrzuci te zdarzenia. Superpaństwo może mieć zdefiniowane przejście dla zdarzenia, które jest odraczane przez podstan. Zgodnie z innymi obszarami specyfikacji maszyn stanu UML, podstan ma pierwszeństwo przed superpaństwem, zdarzenie zostanie odroczone, a przejście dla superpaństwa nie zostanie wykonane. W przypadku regionów ortogonalnych, w których jeden region ortogonalny odracza zdarzenie, a inny konsumuje zdarzenie, pierwszeństwo ma konsument, a zdarzenie jest konsumowane, a nie odraczane.

Ograniczenia maszyn stanowych UML

Karty stanu Harela, które są prekursorami maszyn stanu UML, zostały wynalezione jako „wizualny formalizm dla złożonych systemów”, więc od samego początku były nierozerwalnie związane z graficzną reprezentacją w postaci diagramów stanu. Jednak ważne jest, aby zrozumieć, że koncepcja maszyny stanów UML wykracza poza jakąkolwiek określoną notację, graficzną lub tekstową. Specyfikacja UML sprawia, że ​​to rozróżnienie jest oczywiste poprzez wyraźne oddzielenie semantyki automatu stanowego od notacji.

Jednak notacja wykresów stanu UML nie jest czysto wizualna. Każda nietrywialna maszyna stanu wymaga dużej ilości informacji tekstowych (np. Specyfikacji działań i strażników). Dokładna składnia wyrażeń action i guard nie jest zdefiniowana w specyfikacji UML, więc wiele osób używa strukturalnego języka angielskiego lub, bardziej formalnie, wyrażeń w języku implementacji, takim jak C , C ++ lub Java . W praktyce oznacza to, że zapis stanu UML zależy w dużym stopniu od konkretnego języka programowania .

Niemniej jednak większość semantyki wykresów stanu jest silnie ukierunkowana na zapis graficzny. Na przykład diagramy stanu słabo przedstawiają sekwencję przetwarzania, czy to kolejność oceny strażników, czy kolejność wysyłania zdarzeń do regionów ortogonalnych . Specyfikacja UML omija te problemy, nakładając na projektanta ciężar nie polegania na żadnym konkretnym sekwencjonowaniu. Jednak jest tak, że kiedy maszyny stanów UML są faktycznie wdrażane, nieuchronnie istnieje pełna kontrola nad kolejnością wykonywania, co prowadzi do krytyki, że semantyka UML może być niepotrzebnie restrykcyjna. Podobnie, diagramy stanu wymagają dużej ilości sprzętu hydraulicznego (pseudostany, takie jak połączenia, rozwidlenia, skrzyżowania, punkty wyboru itp.), Aby graficznie przedstawić przepływ sterowania. Innymi słowy, te elementy notacji graficznej nie dodają wiele wartości w przedstawianiu przepływu sterowania w porównaniu ze zwykłym kodem strukturalnym .

Notacja i semantyka UML są naprawdę nastawione na skomputeryzowane narzędzia UML . Maszyna stanów UML, przedstawiona w narzędziu, to nie tylko diagram stanów, ale raczej mieszanina reprezentacji graficznej i tekstowej, która precyzyjnie oddaje zarówno topologię stanu, jak i działania. Użytkownicy narzędzia mogą uzyskać kilka komplementarnych widoków tej samej maszyny stanu, zarówno wizualnych, jak i tekstowych, podczas gdy wygenerowany kod jest tylko jednym z wielu dostępnych widoków.

Zobacz też

Lista aplikacji, które zapewniają dedykowaną obsługę hierarchicznych maszyn o skończonych stanach

Bibliografia

Zewnętrzne linki