Aktualisieren:
Nochmals vielen Dank für die Beispiele, sie waren sehr hilfreich und mit den folgenden möchte ich ihnen nichts wegnehmen.
Sind die derzeit gegebenen Beispiele, soweit ich sie verstehe, und Zustandsmaschinen nicht nur die Hälfte dessen, was wir normalerweise unter einer Zustandsmaschine verstehen?
In dem Sinne, dass die Beispiele den Status ändern, dies jedoch nur durch Ändern des Werts einer Variablen (und Zulassen unterschiedlicher Wertänderungen in unterschiedlichen Zuständen) dargestellt wird, während eine Zustandsmaschine normalerweise auch ihr Verhalten ändern sollte und das Verhalten nicht (nur) in Der Sinn, unterschiedliche Wertänderungen für eine Variable abhängig vom Zustand zuzulassen, aber der Sinn, unterschiedliche Methoden für verschiedene Zustände ausführen zu lassen.
Oder habe ich ein Missverständnis von Zustandsautomaten und ihrer allgemeinen Verwendung?
Freundliche Grüße
Ursprüngliche Frage:
Ich fand diese Diskussion über Zustandsautomaten und Iteratorblöcke in c # und Tools zum Erstellen von Zustandsautomaten und was nicht für C #, also fand ich viele abstrakte Dinge, aber als Noob ist das alles ein wenig verwirrend.
Es wäre also großartig, wenn jemand ein C # -Quellcodebeispiel bereitstellen könnte, das eine einfache Zustandsmaschine mit vielleicht 3,4 Zuständen realisiert, nur um das Wesentliche zu verstehen.
quelle
Antworten:
Beginnen wir mit diesem einfachen Zustandsdiagramm:
Wir haben:
Sie können dies auf verschiedene Arten in C # konvertieren, z. B. indem Sie eine switch-Anweisung für den aktuellen Status und Befehl ausführen oder Übergänge in einer Übergangstabelle nachschlagen. Für diese einfache Zustandsmaschine bevorzuge ich eine Übergangstabelle, die mit a
Dictionary
:Aus persönlichen Gründen entwerfe ich meine Zustandsautomaten gerne mit einer
GetNext
Funktion, um den nächsten Zustand deterministisch zurückzugeben , und einerMoveNext
Funktion, um die Zustandsmaschine zu mutieren.quelle
GetHashCode()
Verwendung von Primzahlen.StateTransition
Klasse wird als Schlüssel im Wörterbuch verwendet und die Gleichheit der Schlüssel ist wichtig. Zwei unterschiedliche Instanzen vonStateTransition
sollten als gleich angesehen werden, solange sie denselben Übergang darstellen (z. B.CurrentState
und gleichCommand
sind). Für die Umsetzung der Gleichstellung haben Sie außer Kraft zu setzenEquals
sowieGetHashCode
. Insbesondere verwendet das Wörterbuch den Hash-Code und zwei gleiche Objekte müssen denselben Hash-Code zurückgeben. Sie erhalten auch eine gute Leistung, wenn nicht zu viele ungleiche Objekte denselben Hash-Code verwenden, weshalbGetHashCode
sie wie gezeigt implementiert werden.Möglicherweise möchten Sie eine der vorhandenen Open-Source-Finite-State-Maschinen verwenden. Beispiel: bbv.Common.StateMachine unter http://code.google.com/p/bbvcommon/wiki/StateMachine . Es hat eine sehr intuitive, fließende Syntax und viele Funktionen wie Eingangs- / Ausgangsaktionen, Übergangsaktionen, Schutzfunktionen, hierarchische, passive Implementierung (auf dem Thread des Aufrufers ausgeführt) und aktive Implementierung (eigener Thread, auf dem der fsm ausgeführt wird). Ereignisse werden einer Warteschlange hinzugefügt).
Am Beispiel von Julia wird die Definition für die Zustandsmaschine sehr einfach:
Update : Der Projektspeicherort wurde verschoben zu: https://github.com/appccelerate/statemachine
quelle
Hier ist ein Beispiel einer sehr klassischen Finite-State-Maschine, die ein sehr vereinfachtes elektronisches Gerät (wie einen Fernseher) modelliert.
quelle
private void DoNothing() {return;}
alle Instanzen von null durch hinzugefügt und ersetztthis.DoNothing
. Hat den angenehmen Nebeneffekt, den aktuellen Zustand wiederherzustellen.States
zuUnpowered, Standby, On
. Meine Argumentation ist, dass wenn mich jemand fragen würde, in welchem Zustand sich mein Fernseher befindet, ich "Aus" und nicht "Start" sagen würde. Ich habe mich auch verändertStandbyWhenOn
undStandbyWhenOff
zuTurnOn
undTurnOff
. Dadurch wird der Code intuitiver gelesen, aber ich frage mich, ob es Konventionen oder andere Faktoren gibt, die meine Terminologie weniger geeignet machen.Einige schamlose Eigenwerbung hier, aber vor einiger Zeit habe ich eine Bibliothek namens YieldMachine erstellt, mit der eine Zustandsmaschine mit begrenzter Komplexität auf sehr saubere und einfache Weise beschrieben werden kann. Stellen Sie sich zum Beispiel eine Lampe vor:
Beachten Sie, dass diese Zustandsmaschine 2 Trigger und 3 Zustände hat. In YieldMachine-Code schreiben wir eine einzige Methode für alle zustandsbezogenen Verhaltensweisen, bei der wir die schreckliche Gräueltat begehen, die
goto
für jeden Zustand verwendet wird. Ein Trigger wird zu einer Eigenschaft oder einem Feld vom TypAction
, das mit einem Attribut namens dekoriert istTrigger
. Ich habe den Code des ersten Zustands und seine Übergänge unten kommentiert. Die nächsten Zustände folgen demselben Muster.Kurz und nett, wie!
Diese Zustandsmaschine wird einfach durch Senden von Triggern an sie gesteuert:
Zur Verdeutlichung habe ich dem ersten Status einige Kommentare hinzugefügt, um Ihnen zu helfen, die Verwendung zu verstehen.
Dies funktioniert, weil der C # -Compiler tatsächlich intern für jede verwendete Methode eine Zustandsmaschine erstellt hat
yield return
. Dieses Konstrukt wird normalerweise verwendet, um träge Datensequenzen zu erstellen. In diesem Fall interessiert uns jedoch nicht die zurückgegebene Sequenz (die ohnehin alle Nullen sind), sondern das Statusverhalten, das unter der Haube erstellt wird.Die
StateMachine
Basisklasse reflektiert die Konstruktion, um jeder[Trigger]
Aktion Code zuzuweisen , wodurch dasTrigger
Element festgelegt und die Zustandsmaschine vorwärts bewegt wird.Aber Sie müssen die Interna nicht wirklich verstehen, um sie verwenden zu können.
quelle
goto
Zwischenmethode zuzulassen .goto
, zwischen Methoden zu springen? Ich sehe nicht ein, wie das möglicherweise funktionieren würde. Nein,goto
ist problematisch, weil es zu einer prozeduralen Programmierung führt (dies allein erschwert nette Dinge wie das Testen von Einheiten), die Wiederholung von Code fördert (bemerkt, wieInvalidTrigger
für jeden Zustand eingefügt werden muss?) Und schließlich den Programmfluss schwieriger zu verfolgen macht. Vergleichen Sie dies mit (den meisten) anderen Lösungen in diesem Thread, und Sie werden sehen, dass dies die einzige ist, bei der der gesamte FSM in einer einzigen Methode ausgeführt wird. Das ist normalerweise genug, um Bedenken zu wecken.goto
ziemlich gut zu.goto
das Wechseln zwischen Funktionen, unterstützt jedoch keine Funktionen? :) Sie haben Recht, die Bemerkung "schwerer zu folgen" ist eher ein allgemeinesgoto
Problem, in diesem Fall in der Tat kein so großes Problem.Sie können einen Iteratorblock codieren, mit dem Sie einen Codeblock orchestriert ausführen können. Wie der Codeblock aufgebrochen wird, muss wirklich nichts entsprechen, es ist nur so, wie Sie ihn codieren möchten. Beispielsweise:
In diesem Fall wird beim Aufrufen von CountToTen noch nichts ausgeführt. Was Sie erhalten, ist effektiv ein Zustandsmaschinengenerator, für den Sie eine neue Instanz der Zustandsmaschine erstellen können. Sie tun dies, indem Sie GetEnumerator () aufrufen. Der resultierende IEnumerator ist effektiv eine Zustandsmaschine, die Sie durch Aufrufen von MoveNext (...) steuern können.
In diesem Beispiel wird beim ersten Aufruf von MoveNext (...) "1" in die Konsole geschrieben, und beim nächsten Aufruf von MoveNext (...) werden 2, 3, 4 und angezeigt dann 5, 6, 7 und dann 8 und dann 9, 10. Wie Sie sehen können, ist dies ein nützlicher Mechanismus, um zu koordinieren, wie die Dinge ablaufen sollen.
quelle
Ich poste hier eine andere Antwort, da dies Zustandsmaschinen aus einer anderen Perspektive sind. sehr visuell.
Meine ursprüngliche Antwort ist klassischer Imperativcode. Ich denke, es ist ziemlich visuell, wie Code geht, wegen des Arrays, das die Visualisierung der Zustandsmaschine einfach macht. Der Nachteil ist, dass Sie das alles schreiben müssen. Die Antwort von Remos verringert den Aufwand beim Schreiben des Kesselplattencodes, ist jedoch weitaus weniger visuell. Es gibt die dritte Alternative; wirklich die Zustandsmaschine zeichnen.
Wenn Sie .NET verwenden und auf Version 4 der Laufzeit abzielen können, haben Sie die Möglichkeit, die Statusmaschinenaktivitäten des Workflows zu verwenden . Mit diesen können Sie im Wesentlichen die Zustandsmaschine zeichnen (ähnlich wie in Julias Diagramm) und von der WF-Laufzeit für Sie ausführen lassen.
Weitere Informationen finden Sie im MSDN-Artikel Erstellen von Statusmaschinen mit Windows Workflow Foundation und auf dieser CodePlex-Site für die neueste Version.
Dies ist die Option, die ich beim Targeting von .NET immer bevorzugen würde, da es für Nicht-Programmierer leicht zu sehen, zu ändern und zu erklären ist. Bilder sagen mehr als tausend Worte!
quelle
Es ist nützlich, sich daran zu erinnern, dass Zustandsmaschinen eine Abstraktion sind und Sie keine bestimmten Werkzeuge benötigen, um eine zu erstellen. Werkzeuge können jedoch nützlich sein.
Sie können beispielsweise eine Zustandsmaschine mit folgenden Funktionen realisieren:
Diese Maschine würde nach Möwen suchen und versuchen, sie mit Wasserballons zu treffen. Wenn es fehlt, wird es versuchen, einen zu feuern, bis es trifft (könnte einige realistische Erwartungen haben;)), andernfalls wird es sich in der Konsole freuen. Es jagt weiter, bis keine Möwen mehr zu belästigen sind.
Jede Funktion entspricht jedem Zustand; Die Start- und Endzustände (oder Akzeptanzzustände ) werden nicht angezeigt. Es gibt dort wahrscheinlich mehr Zustände als von den Funktionen modelliert. Zum Beispiel befindet sich die Maschine nach dem Abfeuern des Ballons wirklich in einem anderen Zustand als zuvor, aber ich entschied, dass diese Unterscheidung unpraktisch war.
Eine übliche Methode besteht darin, Klassen zur Darstellung von Zuständen zu verwenden und diese dann auf unterschiedliche Weise zu verbinden.
quelle
Ich habe dieses großartige Tutorial online gefunden und es hat mir geholfen, meinen Kopf um endliche Zustandsmaschinen zu wickeln.
http://gamedevelopment.tutsplus.com/tutorials/finite-state-machines-theory-and-implementation--gamedev-11867
Das Tutorial ist sprachunabhängig und kann daher problemlos an Ihre C # -Bedürfnisse angepasst werden.
Auch das verwendete Beispiel (eine Ameise, die nach Nahrung sucht) ist leicht zu verstehen.
Aus dem Tutorial:
quelle
Heute bin ich tief in State Design Pattern. Ich habe ThreadState getestet, was (+/-) dem Threading in C # entspricht, wie im Bild aus Threading in C # beschrieben
Sie können ganz einfach neue Status hinzufügen. Das Konfigurieren von Verschiebungen von einem Status in einen anderen ist sehr einfach, da diese in der Statusimplementierung enthalten sind
Implementierung und Verwendung unter: Implementiert .NET ThreadState nach Statusentwurfsmuster
quelle
Ich habe noch nicht versucht, ein FSM in C # zu implementieren, aber all dies klingt (oder sieht) sehr kompliziert aus, wie ich FSMs in der Vergangenheit in einfachen Sprachen wie C oder ASM gehandhabt habe.
Ich glaube, die Methode, die ich immer gekannt habe, heißt so etwas wie eine "iterative Schleife". Darin befindet sich im Wesentlichen eine 'while'-Schleife, die aufgrund von Ereignissen (Interrupts) regelmäßig beendet wird und dann wieder zur Hauptschleife zurückkehrt.
Innerhalb der Interrupt-Handler würden Sie einen CurrentState übergeben und einen NextState zurückgeben, der dann die CurrentState-Variable in der Hauptschleife überschreibt. Sie tun dies unendlich lange, bis das Programm geschlossen wird (oder der Mikrocontroller zurückgesetzt wird).
Was ich bei anderen Antworten sehe, sehen alle sehr kompliziert aus, verglichen mit der Art und Weise, wie ein FSM meiner Meinung nach implementiert werden soll. Seine Schönheit liegt in seiner Einfachheit und FSM kann mit vielen, vielen Zuständen und Übergängen sehr kompliziert sein, aber sie ermöglichen es, komplizierte Prozesse leicht zu zerlegen und zu verdauen.
Mir ist klar, dass meine Antwort keine weitere Frage enthalten sollte, aber ich bin gezwungen zu fragen: Warum scheinen diese anderen vorgeschlagenen Lösungen so kompliziert zu sein?
Sie scheinen einem kleinen Nagel mit einem riesigen Vorschlaghammer zu begegnen.
quelle
Was für ein Kampf gegen StatePattern. Passt das zu Ihren Bedürfnissen?
Ich denke, sein Kontext hängt zusammen, aber es ist auf jeden Fall einen Versuch wert.
http://en.wikipedia.org/wiki/State_pattern
Auf diese Weise können Ihre Bundesstaaten entscheiden, wohin sie gehen möchten, und nicht die "Objekt" -Klasse.
Bruno
quelle
Meiner Meinung nach ist eine Zustandsmaschine nicht nur zum Ändern von Zuständen gedacht, sondern auch (sehr wichtig) zum Behandeln von Triggern / Ereignissen innerhalb eines bestimmten Zustands. Wenn Sie das Entwurfsmuster für Zustandsmaschinen besser verstehen möchten, finden Sie eine gute Beschreibung im Buch Head First Design Patterns, Seite 320 .
Es geht nicht nur um die Zustände innerhalb von Variablen, sondern auch um die Behandlung von Triggern innerhalb der verschiedenen Zustände. Tolles Kapitel (und nein, es gibt keine Gebühr für mich, dies zu erwähnen :-), das nur eine leicht verständliche Erklärung enthält.
quelle
Ich habe gerade Folgendes beigetragen:
https://code.google.com/p/ysharp/source/browse/#svn%2Ftrunk%2FStateMachinesPoC
Hier ist eines der Beispiele, die das direkte und indirekte Senden von Befehlen mit dem Status IObserver (des Signals) demonstrieren und somit auf eine Signalquelle reagieren, IObservable (des Signals):
Hinweis: Dieses Beispiel ist eher künstlich und dient hauptsächlich dazu, eine Reihe von orthogonalen Merkmalen zu demonstrieren. Es sollte selten wirklich notwendig sein, die Statuswertdomäne selbst durch eine vollständige Klasse mithilfe des CRTP zu implementieren (siehe: http://en.wikipedia.org/wiki/Curiously_recurring_template_pattern) ) wie .
Hier ist ein sicherlich einfacherer und wahrscheinlich viel häufigerer Anwendungsfall für die Implementierung (unter Verwendung eines einfachen Aufzählungstyps als Zustandswertdomäne) für dieselbe Zustandsmaschine und mit demselben Testfall:
https://code.google.com/p/ysharp/source/browse/trunk/StateMachinesPoC/WatchingTVSample.cs
'HTH
quelle
StateChange
gelöst? Durch Reflexion? Ist das wirklich notwendig?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 } }
Ich habe diese generische Zustandsmaschine aus Julias Code gemacht. Es funktioniert großartig für mich.
Dies sind die Vorteile:
TState
und erstellenTCommand
.TransitionResult<TState>
, um mehr Kontrolle über die Ausgabeergebnisse von[Try]GetNext()
Methoden zu habenStateTransition
nur verfügbarAddTransition(TState, TCommand, TState)
machen, indem die Arbeit damit erleichtert wirdCode:
Dies ist der Rückgabetyp der TryGetNext-Methode:
Wie benutzt man:
So können Sie
OnlineDiscountStateMachine
aus der generischen Klasse eine erstellen :Definieren Sie eine Aufzählung
OnlineDiscountState
für seine Zustände und eine AufzählungOnlineDiscountCommand
für ihre Befehle.Definieren Sie eine Klasse
OnlineDiscountStateMachine
die von der generischen Klasse abgeleitet ist, indem Sie diese beiden Aufzählungen verwendenLeiten Sie den Konstruktor
base(OnlineDiscountState.InitialState)
so ab, dass der Anfangszustand auf gesetzt istOnlineDiscountState.InitialState
Verwenden Sie
AddTransition
so oft wie nötigVerwenden Sie die abgeleitete Zustandsmaschine
quelle
Ich denke, die von Juliet vorgeschlagene Zustandsmaschine hat einen Fehler: Die Methode GetHashCode kann denselben Hashcode für zwei verschiedene Übergänge zurückgeben, zum Beispiel:
Um diesen Fehler zu vermeiden, sollte die Methode folgendermaßen aussehen:
Alex
quelle
int
Werte der Bereich). DeshalbHashCode
wird immer zusammen mit implementiertEquals
. Wenn die Hash-Codes identisch sind, werden die Objekte mit derEquals
Methode auf exakte Gleichheit überprüft .FiniteStateMachine ist eine einfache Zustandsmaschine, die in C # Link geschrieben ist
Vorteile bei der Nutzung meiner Bibliothek FiniteStateMachine:
Download DLL Download
Beispiel auf LINQPad:
quelle
Ich würde state.cs empfehlen . Ich persönlich habe state.js (die JavaScript-Version) verwendet und bin sehr zufrieden damit. Diese C # -Version funktioniert auf ähnliche Weise.
Sie instanziieren Zustände:
Sie instanziieren einige Übergänge:
Sie definieren Aktionen für Zustände und Übergänge:
Und das war's (so ziemlich). Weitere Informationen finden Sie auf der Website.
quelle
Es gibt 2 beliebte Zustandsmaschinenpakete in NuGet.
Appccelerate.StateMachine (13,6 KB Downloads + 3,82 KB Legacy-Version (bbv.Common.StateMachine))
StateMachineToolkit (1.56K Downloads)
Die Appccelerate-Bibliothek verfügt über eine gute Dokumentation , unterstützt jedoch nicht .NET 4, sodass ich für mein Projekt StateMachineToolkit ausgewählt habe.
quelle
Eine andere Alternative in diesem Repo https://github.com/lingkodsoft/StateBliss verwendet fließende Syntax, unterstützt Trigger.
quelle
Sie können meine Lösung verwenden, dies ist der bequemste Weg. Es ist auch kostenlos.
Erstellen Sie die Zustandsmaschine in drei Schritten:
1. Erstellen Sie ein Schema im Knoteneditor und laden Sie es mithilfe der Bibliothek in Ihr Projekt
2. Beschreiben Sie Ihre App-Logik für Ereignisse
3. Führen Sie die Zustandsmaschine aus
Links:
Knoteneditor: https://github.com/SimpleStateMachine/SimpleStateMachineNodeEditor
Bibliothek: https://github.com/SimpleStateMachine/SimpleStateMachineLibrary
quelle