Musimy wdrożyć prostą maszynę stanów w C .
Czy standardowe oświadczenie dotyczące przełącznika jest najlepszym rozwiązaniem?
Mamy aktualny stan (stan) i wyzwalacz przejścia.
switch(state)
{
case STATE_1:
state = DoState1(transition);
break;
case STATE_2:
state = DoState2(transition);
break;
}
...
DoState2(int transition)
{
// Do State Work
...
if(transition == FROM_STATE_2) {
// New state when doing STATE 2 -> STATE 2
}
if(transition == FROM_STATE_1) {
// New State when moving STATE 1 -> STATE 2
}
return new_state;
}
Czy istnieje lepszy sposób na proste maszyny stanowe
EDYCJA: W przypadku C ++ myślę, że biblioteka Boost Statechart może być drogą do zrobienia. Jednak to nie pomaga w przypadku C. Skoncentrujmy się na przypadku użycia C.
c
design-patterns
finite-automata
Benoit
źródło
źródło
Odpowiedzi:
Wolę używać podejścia opartego na tabeli dla większości maszyn stanowych:
Można to oczywiście rozszerzyć, aby obsługiwać wiele automatów stanowych itp. Można również uwzględnić działania przejścia:
Podejście oparte na tabelach jest łatwiejsze do utrzymania i rozszerzania oraz prostsze do odwzorowania na diagramach stanu.
źródło
Być może widziałeś moją odpowiedź na inne pytanie w C, w którym wspomniałem o FSM! Oto jak to robię:
Ze zdefiniowanymi następującymi makrami
Można to zmodyfikować w celu dostosowania do konkretnego przypadku. Na przykład, możesz mieć plik
FSMFILE
, którym chcesz sterować FSM, więc możesz włączyć akcję odczytu następnego znaku do samego makra:teraz masz dwa rodzaje przejść: jeden przechodzi do stanu i odczytuje nowy znak, a drugi przechodzi do stanu bez zużywania jakichkolwiek danych wejściowych.
Możesz także zautomatyzować obsługę EOF za pomocą czegoś takiego:
Zaletą tego podejścia jest to, że możesz bezpośrednio przetłumaczyć diagram stanu, który narysujesz, na działający kod i odwrotnie, możesz łatwo narysować diagram stanu z kodu.
W innych technikach implementacji FSM struktura przejść jest ukryta w strukturach kontrolnych (while, if, switch ...) i kontrolowana przez wartość zmiennych (zazwyczaj
state
zmienna), a powiązanie ładnego diagramu z a może być złożonym zadaniem. zawiły kod.Nauczyłem się tej techniki z artykułu, który ukazał się w wielkim magazynie „Computer Language”, który niestety nie jest już publikowany.
źródło
Użyłem również podejścia tabeli. Jednak istnieje obciążenie. Po co przechowywać drugą listę wskaźników? Funkcja w C bez () jest wskaźnikiem do stałej. Możesz więc:
Oczywiście w zależności od twojego współczynnika strachu (tj. Bezpieczeństwo vs prędkość) możesz chcieć sprawdzić poprawne wskazówki. W przypadku maszyn stanowych większych niż trzy lub więcej stanów powyższe podejście powinno obejmować mniej instrukcji niż równoważne podejście do przełącznika lub tabeli. Możesz nawet makroizować jako:
Czuję również na przykładzie OP, że istnieje uproszczenie, które należy zrobić, myśląc o / projektowaniu automatu stanowego. Nie uważam, że stan przejściowy powinien być używany do logiki. Każda funkcja państwa powinna być w stanie pełnić swoją rolę bez wyraźnej wiedzy o przeszłych stanach. Zasadniczo projektujesz sposób przejścia ze stanu, w którym się znajdujesz, do innego stanu.
Wreszcie, nie zaczynaj projektowania automatu stanowego w oparciu o granice „funkcjonalne”, użyj do tego podfunkcji. Zamiast tego podziel stany na podstawie tego, kiedy będziesz musiał poczekać, aż coś się wydarzy, zanim będziesz mógł kontynuować. Pomoże to zminimalizować liczbę uruchomień automatu stanowego, zanim otrzymasz wynik. Może to być ważne podczas pisania funkcji we / wy lub obsługi przerwań.
Ponadto kilka zalet i wad klasycznej instrukcji przełącznika:
Plusy:
Cons:
Zwróć uwagę na dwa atrybuty, które są zarówno za, jak i przeciw. Myślę, że zmiana stwarza okazję do zbyt dużego współdzielenia między stanami, a współzależność między stanami może stać się niemożliwa do zarządzania. Jednak w przypadku niewielkiej liczby stanów może być najbardziej czytelny i łatwy w utrzymaniu.
źródło
W przypadku prostego automatu stanów użyj po prostu instrukcji switch i typu wyliczenia dla swojego stanu. Wykonuj przejścia wewnątrz instrukcji switch na podstawie wprowadzonych danych. W prawdziwym programie oczywiście zmieniłbyś "if (wejście)", aby sprawdzić punkty przejścia. Mam nadzieję że to pomoże.
źródło
W UML Distilled Martina Fowlera stwierdza (gra słów nie jest przeznaczona) w Rozdziale 10 Diagramy Maszyn Stanowych (wyróżnienie moje):
Skorzystajmy z uproszczonego przykładu stanów wyświetlacza telefonu komórkowego:
Zagnieżdżony przełącznik
Fowler podał przykład kodu C #, ale dostosowałem go do mojego przykładu.
Wzór stanu
Oto implementacja mojego przykładu ze wzorcem GoF State:
Tabele stanów
Czerpiąc inspirację z Fowlera, oto mój przykład:
Porównanie
Zagnieżdżony przełącznik utrzymuje całą logikę w jednym miejscu, ale kod może być trudny do odczytania, gdy występuje wiele stanów i przejść. Jest prawdopodobnie bezpieczniejszy i łatwiejszy do zweryfikowania niż inne podejścia (bez polimorfizmu lub interpretacji).
Implementacja wzorca stanu potencjalnie rozkłada logikę na kilka oddzielnych klas, co może sprawić, że zrozumienie jej jako całości będzie problemem. Z drugiej strony, małe klasy są łatwe do zrozumienia oddzielnie. Projekt jest szczególnie delikatny, jeśli zmienisz zachowanie, dodając lub usuwając przejścia, ponieważ są to metody w hierarchii i może być wiele zmian w kodzie. Jeśli żyjesz zgodnie z zasadą projektowania małych interfejsów, zobaczysz, że ten wzorzec nie działa tak dobrze. Jeśli jednak automat stanowy jest stabilny, takie zmiany nie będą potrzebne.
Podejście oparte na tabelach stanów wymaga napisania jakiegoś interpretera dla treści (może to być łatwiejsze, jeśli masz refleksję w języku, którego używasz), co może wymagać dużo pracy z góry. Jak zauważa Fowler, jeśli twoja tabela jest oddzielona od twojego kodu, możesz modyfikować zachowanie swojego oprogramowania bez ponownej kompilacji. Ma to jednak pewne konsekwencje dla bezpieczeństwa; oprogramowanie zachowuje się w oparciu o zawartość zewnętrznego pliku.
Edytuj (raczej nie dla języka C)
Istnieje również płynny interfejs (znany również jako wewnętrzny język specyficzny dla domeny), który prawdopodobnie jest ułatwiony przez języki, które mają funkcje pierwszej klasy . Biblioteka Stateless istnieje i że blog pokazuje prosty przykład z kodem. Implementacja Javy (pre Java8) jest omawiany. Pokazano mi również przykład Pythona na GitHubie .
źródło
istnieje również siatka logiczna, która jest łatwiejsza do utrzymania w miarę powiększania się maszyny stanu
źródło
W prostych przypadkach możesz zastosować metodę zmiany stylu. W przeszłości odkryłem, że dobrze sprawdza się również w przypadku przejść:
Nie wiem nic o bibliotece boost, ale tego typu podejście jest banalnie proste, nie wymaga żadnych zewnętrznych zależności i jest łatwe do wdrożenia.
źródło
switch () to potężny i standardowy sposób implementacji maszyn stanu w C, ale może zmniejszyć łatwość konserwacji, jeśli masz dużą liczbę stanów. Inną popularną metodą jest użycie wskaźników funkcji do przechowywania następnego stanu. Ten prosty przykład implementuje przerzutnik ustawiania / resetowania:
źródło
Znalazłem naprawdę zgrabną implementację Moore FSM w C na kursie edx.org Embedded Systems - Shape the World UTAustinX - UT.6.02x, rozdział 10, autorstwa Jonathana Valvano i Ramesha Yerraballi ....
źródło
Możesz zajrzeć do oprogramowania generatora Libero FSM. Z języka opisu stanu i / lub edytora diagramów stanu (Windows) możesz wygenerować kod dla C, C ++, java i wielu innych ... plus ładną dokumentację i diagramy. Źródło i pliki binarne z iMatix
źródło
Ten artykuł jest dobry dla wzorca stanu (chociaż jest to C ++, a nie C).
Jeśli możesz położyć ręce na książce „ Head First Design Patterns ”, wyjaśnienie i przykład są bardzo jasne.
źródło
Jednym z moich ulubionych wzorów jest wzór projektowania państwowego. Reaguj lub zachowuj się inaczej w przypadku tego samego zestawu danych wejściowych.
Jednym z problemów związanych z używaniem instrukcji switch / case dla maszyn stanu jest to, że gdy tworzysz więcej stanów, przełącznik / case staje się trudniejszy / nieporęczny do odczytania / utrzymania, promuje niezorganizowany kod spaghetti i coraz trudniej jest go zmienić bez zepsucia czegoś. Uważam, że używanie wzorców projektowych pomaga mi lepiej organizować dane, co jest celem abstrakcji. Zamiast projektować kod stanu w oparciu o stan, z którego pochodzisz, skonstruuj kod tak, aby rejestrował stan, gdy wchodzisz w nowy stan. W ten sposób skutecznie uzyskasz zapis swojego poprzedniego stanu. Podoba mi się odpowiedź @ JoshPetita i poszedłem o krok dalej, zaczerpnięte prosto z książki GoF:
stateCtxt.h:
stateCtxt.c:
statehandlers.h:
statehandlers.c:
W przypadku większości maszyn stanowych, zwł. Maszyny skończone, każdy stan będzie wiedział, jaki powinien być jego następny stan i jakie są kryteria przejścia do następnego stanu. W przypadku projektów stanu swobodnego może tak nie być, stąd opcja udostępnienia API dla stanów przejściowych. Jeśli chcesz więcej abstrakcji, każdy program obsługi stanu może zostać oddzielony do własnego pliku, który jest równoważny z konkretnymi procedurami obsługi stanu w książce GoF. Jeśli twój projekt jest prosty i ma tylko kilka stanów, wówczas zarówno stateCtxt.c, jak i statehandlers.c mogą zostać połączone w jeden plik dla uproszczenia.
źródło
Z mojego doświadczenia wynika, że użycie instrukcji „przełącznik” jest standardowym sposobem obsługi wielu możliwych stanów. Chociaż jestem zaskoczony, że przekazujesz wartość przejścia do przetwarzania na stan. Myślałem, że sednem automatu stanowego jest to, że każdy stan wykonuje jedną akcję. Następnie następne działanie / wejście określa, w który nowy stan przejść. Spodziewałbym się więc, że każda funkcja przetwarzania stanu natychmiast wykona wszystko, co jest ustalone dla wejścia w stan, a następnie zdecyduje, czy potrzebne jest przejście do innego stanu.
źródło
Istnieje książka zatytułowana Practical Statecharts in C / C ++ . Jednak jest to droga zbyt dużej gramaturze do czego potrzebujemy.
źródło
W przypadku kompilatora, który obsługuje
__COUNTER__
, możesz ich użyć do prostych (ale dużych) maszyn stanu.Zaletą używania
__COUNTER__
zamiast zakodowanych liczb jest to, że możesz dodawać stany w środku innych stanów, bez ponownego numerowania wszystkiego za każdym razem. Jeśli kompilator nie obsługuje__COUNTER__
, w ograniczony sposób można go używać ostrożnie__LINE__
źródło
__COUNTER__
eliminuje potrzebę zmiany numeracji, ponieważ prekompilator wykonuje numerację podczas kompilacji.Możesz użyć minimalistycznej struktury maszyny stanów UML w c. https://github.com/kiishor/UML-State-Machine-in-C
Obsługuje skończoną i hierarchiczną maszynę stanów. Ma tylko 3 API, 2 struktury i 1 wyliczenie.
Maszyna stanów jest reprezentowana przez
state_machine_t
strukturę. Jest to abstrakcyjna struktura, którą można odziedziczyć w celu stworzenia maszyny stanu.Stan jest reprezentowany przez wskaźnik do
state_t
struktury w ramach.Jeśli struktura jest skonfigurowana dla maszyny skończonej, a następnie
state_t
zawiera,Struktura zapewnia interfejs API
dispatch_event
do wysyłania zdarzenia do automatu stanowego i dwa interfejsy API do przechodzenia do stanu.Aby uzyskać więcej informacji na temat implementacji hierarchicznej maszyny stanów, zapoznaj się z repozytorium GitHub.
przykłady kodu
https://github.com/kiishor/UML-State-Machine-in-C/blob/master/demo/simple_state_machine/readme.md
https://github.com/kiishor/UML-State-Machine-in -C / blob / master / demo / simple_state_machine_enhanced / readme.md
źródło
W C ++ rozważ wzorzec stanu .
źródło
Twoje pytanie jest podobne do „czy istnieje typowy wzorzec implementacji bazy danych”? Odpowiedź zależy od tego, co chcesz osiągnąć? Jeśli chcesz zaimplementować większą deterministyczną maszynę stanów, możesz użyć modelu i generatora automatu stanów. Przykłady można obejrzeć na www.StateSoft.org - SM Gallery. Janusz Dobrowolski
źródło
Boost posiada bibliotekę stanów. http://www.boost.org/doc/libs/1_36_0/libs/statechart/doc/index.html
Nie mogę jednak mówić o jego wykorzystaniu. Sam tego nie używałem (jeszcze)
źródło