Aktualizacja:
Ponownie dziękuję za przykłady, które były bardzo pomocne i dlatego nie zamierzam niczego im odbierać.
Czy podane obecnie przykłady, o ile je rozumiem i automaty stanów, nie stanowią tylko połowy tego, co zwykle rozumiemy przez automaty stanów?
W tym sensie, że przykłady zmieniają stan, ale jest to reprezentowane tylko przez zmianę wartości zmiennej (i zezwalanie na różne zmiany wartości w różnych stanach), podczas gdy zwykle maszyna stanów powinna również zmieniać swoje zachowanie, a zachowanie nie (tylko) w poczucie umożliwienia różnych zmian wartości dla zmiennej w zależności od stanu, ale w sensie umożliwienia wykonania różnych metod dla różnych stanów.
Czy mam błędne wyobrażenie o automatach stanowych i ich powszechnym użyciu?
Z poważaniem
Oryginalne pytanie:
Znalazłem tę dyskusję o automatach stanowych i blokach iteratora w c # i narzędziach do tworzenia automatów stanowych, a co nie w języku C #, więc znalazłem wiele abstrakcyjnych rzeczy, ale jako noob wszystko to jest trochę mylące.
Byłoby więc wspaniale, gdyby ktoś mógł podać przykładowy kod źródłowy w języku C #, który realizuje prostą maszynę stanów z być może 3,4 stanami, tylko po to, aby uzyskać sedno.
źródło
Odpowiedzi:
Zacznijmy od tego prostego diagramu stanu:
Mamy:
Możesz przekonwertować to na C # na kilka sposobów, np. Wykonując instrukcję switch na bieżącym stanie i poleceniu lub przeglądając przejścia w tabeli przejścia. W przypadku tej prostej maszyny stanów wolę tabelę przejścia, którą bardzo łatwo jest przedstawić za pomocą
Dictionary
:W ramach osobistych preferencji lubię projektować moje maszyny stanów z
GetNext
funkcją deterministycznego zwracania następnego stanu orazMoveNext
funkcją mutowania maszyny stanów.źródło
GetHashCode()
używania liczb pierwszych.StateTransition
Klasa jest używana jako klucz w słowniku, a równość kluczy jest ważna. Dwa odrębne przypadkiStateTransition
należy uznać za równe, o ile reprezentują to samo przejście (np.CurrentState
ICommand
są takie same). W celu realizacji równości trzeba zastąpićEquals
, jak równieżGetHashCode
. W szczególności słownik użyje kodu skrótu, a dwa równe obiekty muszą zwrócić ten sam kod skrótu. Uzyskujesz również dobrą wydajność, jeśli nie wiele zbyt różnych obiektów ma ten sam kod skrótu, dlategoGetHashCode
jest implementowany tak, jak pokazano.Możesz użyć jednego z istniejących skończonych automatów stanów. Np. Bbv.Common.StateMachine można znaleźć na stronie http://code.google.com/p/bbvcommon/wiki/StateMachine . Ma bardzo intuicyjną płynną składnię i wiele funkcji, takich jak akcje wejścia / wyjścia, akcje przejścia, zabezpieczenia, hierarchiczna, pasywna implementacja (wykonywana w wątku wywołującego) i aktywna implementacja (własny wątek, na którym działa fsm, zdarzenia są dodawane do kolejki).
Na przykładzie Juliets definicja maszyny stanowej staje się bardzo łatwa:
Aktualizacja : Lokalizacja projektu została przeniesiona do: https://github.com/appccelerate/statemachine
źródło
Oto przykład bardzo klasycznej skończonej maszyny stanów, modelującej bardzo uproszczone urządzenie elektroniczne (jak telewizor)
źródło
private void DoNothing() {return;}
i zastąpiłem wszystkie wystąpienia wartości null wartościąthis.DoNothing
. Ma przyjemny efekt uboczny powrotu do obecnego stanu.States
naUnpowered, Standby, On
. Moje rozumowanie jest takie, że gdyby ktoś zapytał mnie, w jakim stanie jest moja telewizja, powiedziałbym „Wyłącz”, a nie „Rozpocznij”. Zmieniłem równieżStandbyWhenOn
iStandbyWhenOff
doTurnOn
iTurnOff
. Czyni to kod bardziej intuicyjnym, ale zastanawiam się, czy istnieją konwencje lub inne czynniki, które czynią moją terminologię mniej odpowiednią.Niektóre bezwstydne autopromocje tutaj, ale jakiś czas temu stworzyłem bibliotekę o nazwie YieldMachine, która pozwala na opisanie maszyny stanów o ograniczonej złożoności w bardzo czysty i prosty sposób. Rozważmy na przykład lampę:
Zauważ, że ta maszyna stanów ma 2 wyzwalacze i 3 stany. W kodzie YieldMachine piszemy jedną metodę dla wszystkich zachowań związanych ze stanem, w której popełniamy okropne okrucieństwo przy stosowaniu
goto
dla każdego stanu. Wyzwalacz staje się właściwością lub polem typuAction
ozdobionym atrybutem o nazwieTrigger
. Skomentowałem kod pierwszego stanu i jego przejścia poniżej; kolejne stany mają ten sam wzór.Krótko i miło, eh!
Ta maszyna stanów jest kontrolowana po prostu przez wysyłanie do niej wyzwalaczy:
Aby to wyjaśnić, dodałem kilka komentarzy do pierwszego stanu, aby pomóc ci zrozumieć, jak z tego korzystać.
Działa to, ponieważ kompilator C # faktycznie stworzył maszynę stanu wewnętrznie dla każdej używanej metody
yield return
. Konstrukt ten jest zwykle używany do leniwego tworzenia sekwencji danych, ale w tym przypadku tak naprawdę nie jesteśmy zainteresowani zwróconą sekwencją (która i tak ma wartość zerową), ale zachowaniem stanu, które powstaje pod maską.StateMachine
Klasa bazowa robi jakąś refleksję na temat budowy do kodu przypisać do każdego[Trigger]
działania, które ustawiaTrigger
element i przesuwa stan maszyny do przodu.Ale tak naprawdę nie musisz rozumieć wewnętrznych elementów, aby móc z niego korzystać.
źródło
goto
między metodami.goto
przeskakiwać między metodami? Nie rozumiem, jak by to mogło działać. Nie,goto
jest problematyczne, ponieważ powoduje programowanie proceduralne (samo w sobie komplikuje to miłe rzeczy, takie jak testowanie jednostkowe), promuje powtarzanie kodu (zauważyłeś, jakInvalidTrigger
należy wstawić dla każdego stanu?) I ostatecznie utrudnia śledzenie programu. Porównaj to z (większością) innymi rozwiązaniami w tym wątku, a zobaczysz, że jest to jedyne, w którym cały FSM dzieje się za pomocą jednej metody. Zwykle to wystarcza, aby wzbudzić obawy.goto
całkiem dobrze odwzorowuje .goto
na przeskakiwanie między funkcjami, ale nie obsługuje funkcji? :) Masz rację, uwaga „trudniejsza do naśladowania” jest bardziej ogólnągoto
kwestią, w tym przypadku nie stanowi większego problemu.Możesz zakodować blok iteratora, który pozwala na wykonanie bloku kodu w zorganizowany sposób. To, w jaki sposób blok kodu jest rozbity, tak naprawdę wcale nie musi odpowiadać, po prostu chcesz go kodować. Na przykład:
W takim przypadku po wywołaniu CountToTen nic się jeszcze nie wykonuje. Dostajesz w efekcie generator maszyny stanów, dla którego możesz utworzyć nową instancję maszyny stanu. Robisz to, wywołując funkcję GetEnumerator (). Wynikowy IEnumerator jest w rzeczywistości maszyną stanu, którą można prowadzić, wywołując MoveNext (...).
Dlatego w tym przykładzie przy pierwszym wywołaniu MoveNext (...) zobaczysz napis „1” zapisany na konsoli, a przy następnym wywołaniu MoveNext (...) zobaczysz 2, 3, 4 i następnie 5, 6, 7, a następnie 8, a następnie 9, 10. Jak widać, jest to przydatny mechanizm do koordynowania tego, jak rzeczy powinny się zdarzyć.
źródło
Podaję tutaj inną odpowiedź, ponieważ są to maszyny stanowe z innej perspektywy; bardzo wizualnie.
Moja oryginalna odpowiedź to klasyczny kod rozkazujący. Wydaje mi się, że kod jest dość wizualny ze względu na tablicę, która ułatwia wizualizację automatu stanów. Minusem jest to, że musisz to wszystko napisać. Remos zmniejsza wysiłek związany z pisaniem kodu płyty kotła, ale jest znacznie mniej wizualna. Istnieje trzecia alternatywa; naprawdę rysuje maszynę stanu.
Jeśli używasz platformy .NET i możesz kierować na wersję 4 środowiska wykonawczego, możesz skorzystać z działań maszyny stanu przepływu pracy . Te w istocie pozwalają narysować maszynę państwową (podobnie jak u Julii diagramie ) i umożliwić wykonanie jej przez WF.
Zobacz artykuł MSDN Budowanie automatów stanowych za pomocą Windows Workflow Foundation, aby uzyskać więcej informacji, oraz tę witrynę CodePlex dla najnowszej wersji.
Jest to opcja, którą zawsze wolałbym podczas atakowania na platformę .NET, ponieważ jest łatwa do zobaczenia, zmiany i wyjaśnienia osobom niebędącym programistami; zdjęcia są warte tysiąca słów, jak mówią!
źródło
Warto pamiętać, że maszyny stanowe są abstrakcją i nie potrzebujesz specjalnych narzędzi do ich tworzenia, jednak narzędzia mogą być przydatne.
Możesz na przykład zrealizować maszynę stanu z funkcjami:
Ta maszyna poluje na mewy i próbuje uderzyć je balonami wodnymi. Jeśli nie trafi, spróbuje odpalić jeden, dopóki nie trafi (może to przynieść pewne realistyczne oczekiwania;)), w przeciwnym razie będzie się chełpił w konsoli. Kontynuuje polowanie, dopóki nie opuści mew.
Każda funkcja odpowiada każdemu stanowi; stany początkowy i końcowy (lub akceptacja ) nie są wyświetlane. Prawdopodobnie jest tam więcej stanów niż modelowanych przez funkcje. Na przykład po odpaleniu balonu maszyna jest naprawdę w innym stanie niż przedtem, ale uznałem, że to rozróżnienie jest niepraktyczne.
Częstym sposobem jest użycie klas do reprezentowania stanów, a następnie połączenie ich na różne sposoby.
źródło
Znalazłem ten świetny samouczek online i pomógł mi owinąć głowę wokół maszyn o skończonych stanach.
http://gamedevelopment.tutsplus.com/tutorials/finite-state-machines-theory-and-implementation--gamedev-11867
Samouczek jest niezależny od języka, dzięki czemu można go łatwo dostosować do potrzeb języka C #.
Również zastosowany przykład (mrówka poszukująca jedzenia) jest łatwy do zrozumienia.
Z samouczka:
źródło
Dziś jestem głęboko w State Design Pattern. Zrobiłem i przetestowałem ThreadState, który jest równy (+/-) wątkowi w C #, jak opisano na zdjęciu z wątku w C #
Możesz łatwo dodawać nowe stany, konfigurować przejścia z jednego stanu do drugiego jest bardzo łatwe, ponieważ jest on zawarty w implementacji stanu
Implementacja i użycie w: Implementuje .NET ThreadState według stanu wzorca projektowego
źródło
Nie próbowałem jeszcze implementować FSM w C #, ale wszystkie te dźwięki (lub wyglądają) są bardzo skomplikowane w stosunku do sposobu, w jaki obsługiwałem FSM w przeszłości w językach niskiego poziomu, takich jak C lub ASM.
Uważam, że metoda, którą zawsze znałem, nazywa się „pętlą iteracyjną”. Zasadniczo masz w nim pętlę „while”, która okresowo kończy się na podstawie zdarzeń (przerwań), a następnie wraca do głównej pętli.
W ramach procedur obsługi przerwań należy przekazać wartość CurrentState i zwrócić wartość NextState, która następnie zastąpi zmienną CurrentState w głównej pętli. Robisz to ad infinitum, dopóki program się nie zamknie (lub mikrokontroler się nie zresetuje).
To, co widzę w innych odpowiedziach, wygląda na bardzo skomplikowane w porównaniu z tym, w jaki sposób FSM jest moim zdaniem przeznaczony do wdrożenia; jego piękno tkwi w jego prostocie, a FSM może być bardzo skomplikowany w wielu, wielu stanach i przejściach, ale pozwalają one łatwo rozłożyć i strawić skomplikowany proces.
Zdaję sobie sprawę, że moja odpowiedź nie powinna zawierać innego pytania, ale jestem zmuszona zapytać: dlaczego te inne proponowane rozwiązania wydają się tak skomplikowane?
Wydaje się, że są podobni do uderzenia w mały gwóźdź gigantycznym młotem saneczkowym.
źródło
Co za stan StatePattern. Czy to pasuje do twoich potrzeb?
Myślę, że ma to związek z kontekstem, ale na pewno warto spróbować.
http://en.wikipedia.org/wiki/State_pattern
To pozwala twoim stanom decydować, dokąd pójść, a nie klasie „object”.
Bruno
źródło
Moim zdaniem maszyna stanów służy nie tylko do zmiany stanów, ale także (bardzo ważne) do obsługi wyzwalaczy / zdarzeń w określonym stanie. Jeśli chcesz lepiej zrozumieć wzorzec projektowy maszyny stanów, dobry opis można znaleźć w książce Wzory projektowania pierwszego głowy, strona 320 .
Nie chodzi tylko o stany w zmiennych, ale także o obsługę wyzwalaczy w różnych stanach. Świetny rozdział (i nie, nie wspominam o tym za :-), który zawiera tylko łatwe do zrozumienia wyjaśnienie.
źródło
Właśnie włączyłem to:
https://code.google.com/p/ysharp/source/browse/#svn%2Ftrunk%2FStateMachinesPoC
Oto jeden z przykładów demonstrujących bezpośrednie i pośrednie wysyłanie poleceń, ze stanami jak IObserver (sygnału), a zatem odpowiada na źródło sygnału, IObservable (sygnału):
Uwaga: ten przykład jest raczej sztuczny i ma na celu głównie pokazanie szeregu funkcji ortogonalnych. Rzadko powinna istnieć rzeczywista potrzeba implementacji samej domeny wartości stanu za pomocą pełnej klasy, przy użyciu CRTP (patrz: http://en.wikipedia.org/wiki/Curiously_recurring_template_pattern ) w ten sposób.
Oto z pewnością prostszy i prawdopodobnie znacznie bardziej powszechny przypadek użycia implementacji (użycie prostego typu wyliczenia jako domeny wartości stanów), dla tego samego automatu stanów i z tym samym przypadkiem testowym:
https://code.google.com/p/ysharp/source/browse/trunk/StateMachinesPoC/WatchingTVSample.cs
„HTH
źródło
StateChange
rozwiązać? Poprzez odbicie? Czy to naprawdę konieczne?private Television(string moniker, Television value) { Handler<Television, TvOperation, DateTime> myHandler = StateChange; // (code omitted) new { From = Television.Unplugged, When = TvOperation.Plug, Goto = Television.Off, With = myHandler } }
Zrobiłem tę maszynę stanu ogólnego z kodu Juliet. Działa dla mnie niesamowicie.
Oto zalety:
TState
iTCommand
,TransitionResult<TState>
aby mieć większą kontrolę nad wynikami wyjściowymi[Try]GetNext()
metodStateTransition
tylko poprzezAddTransition(TState, TCommand, TState)
ułatwienie pracy z niąKod:
To jest zwracany typ metody TryGetNext:
Jak używać:
W ten sposób możesz stworzyć
OnlineDiscountStateMachine
klasę ogólną:Zdefiniuj wyliczenie
OnlineDiscountState
dla swoich stanów i wyliczenieOnlineDiscountCommand
dla jego poleceń.Zdefiniuj klasę
OnlineDiscountStateMachine
wywodzącą się z klasy ogólnej za pomocą tych dwóch wyliczeńWyprowadzić konstruktor,
base(OnlineDiscountState.InitialState)
aby stan początkowy był ustawiony naOnlineDiscountState.InitialState
Używaj
AddTransition
tyle razy, ile potrzebaużyj pochodnej maszyny stanów
źródło
Myślę, że maszyna stanu zaproponowana przez Juliet ma błąd: metoda GetHashCode może zwrócić ten sam kod skrótu dla dwóch różnych przejść, na przykład:
Aby uniknąć tego błędu, metoda powinna wyglądać następująco:
Alex
źródło
int
wartości). DlategoHashCode
zawsze jest wdrażany wraz zEquals
. Jeśli kody skrótu są takie same, obiekty są sprawdzane pod kątem dokładności za pomocąEquals
metody.FiniteStateMachine to prosta maszyna stanowa, napisana w C # Link
Zalety korzystania z mojej biblioteki FiniteStateMachine:
Pobierz DLL do pobrania
Przykład na LINQPad:
źródło
Polecam state.cs . Ja osobiście użyłem state.js (wersja JavaScript) i jestem z tego bardzo zadowolony. Ta wersja C # działa w podobny sposób.
Tworzysz stany:
Tworzysz niektóre przejścia:
Definiujesz działania dotyczące stanów i przejść:
I to jest (właściwie) to. Więcej informacji znajdziesz na stronie internetowej.
źródło
W NuGet są 2 popularne pakiety automatów stanowych.
Appccelerate.StateMachine (13,6 tys. Pobrań + 3,82 tys. Starszej wersji (bbv.Common.StateMachine))
StateMachineToolkit (1,56 KB pobrań)
Biblioteka Appccelerate ma dobrą dokumentację , ale nie obsługuje platformy .NET 4, więc dla mojego projektu wybrałem StateMachineToolkit.
źródło
Inna alternatywa w tym repozytorium https://github.com/lingkodsoft/StateBliss używa płynnej składni, obsługuje wyzwalacze.
źródło
Możesz skorzystać z mojego rozwiązania, jest to najwygodniejszy sposób. Jest również bezpłatny.
Utwórz maszynę stanu w trzech krokach:
1. Utwórz schemat w edytorze węzłów🔗 i załaduj go do projektu za pomocą biblioteki📚
2. Opisz logikę aplikacji dotyczącą zdarzeń⚡
3. Uruchom maszynę stanu🚘
Spinki do mankietów:
Edytor węzłów: https://github.com/SimpleStateMachine/SimpleStateMachineNodeEditor
Biblioteka: https://github.com/SimpleStateMachine/SimpleStateMachineLibrary
źródło