Actualizar:
Nuevamente, gracias por los ejemplos, han sido muy útiles y con lo siguiente no pretendo quitarles nada.
¿No son los ejemplos que se dan actualmente, por lo que yo entiendo y las máquinas de estados, solo la mitad de lo que generalmente entendemos por una máquina de estados?
En el sentido de que los ejemplos cambian de estado, pero eso solo se representa cambiando el valor de una variable (y permitiendo diferentes cambios de valor en diferentes estados), mientras que generalmente una máquina de estados también debe cambiar su comportamiento, y el comportamiento no (solo) en el sentido de permitir diferentes cambios de valor para una variable dependiendo del estado, pero en el sentido de permitir que se ejecuten diferentes métodos para diferentes estados.
¿O tengo una idea errónea de las máquinas de estado y su uso común?
Atentamente
Pregunta original
Encontré esta discusión sobre máquinas de estado y bloques iteradores en C # y herramientas para crear máquinas de estado y qué no para C #, así que encontré muchas cosas abstractas, pero como novato, todo esto es un poco confuso.
Por lo tanto, sería genial si alguien pudiera proporcionar un código fuente de ejemplo de C # que se dé cuenta de una máquina de estado simple con quizás 3,4 estados, solo para entenderlo.
fuente
Respuestas:
Comencemos con este simple diagrama de estado:
Tenemos:
Puede convertir esto a C # de varias maneras, como realizar una declaración de cambio en el estado y comando actuales, o buscar transiciones en una tabla de transición. Para esta máquina de estado simple, prefiero una tabla de transición, que es muy fácil de representar con
Dictionary
:Como cuestión de preferencia personal, me gusta diseñar mis máquinas de estado con una
GetNext
función para devolver el siguiente estado de manera determinista , y unaMoveNext
función para mutar la máquina de estado.fuente
GetHashCode()
uso de primos.StateTransition
clase se usa como clave en el diccionario y la igualdad de claves es importante. Dos instancias distintas deStateTransition
deben considerarse iguales siempre que representen la misma transición (por ejemplo,CurrentState
yCommand
sean iguales). Para implementar la igualdad, debe anularEquals
tambiénGetHashCode
. En particular, el diccionario usará el código hash y dos objetos iguales deben devolver el mismo código hash. También obtiene un buen rendimiento si no demasiados objetos no iguales comparten el mismo código hash, por lo queGetHashCode
se implementa como se muestra.Es posible que desee utilizar una de las máquinas de estado finito de código abierto existentes. Por ejemplo, bbv.Common.StateMachine se encuentra en http://code.google.com/p/bbvcommon/wiki/StateMachine . Tiene una sintaxis fluida muy intuitiva y muchas características como, acciones de entrada / salida, acciones de transición, guardias, implementación jerárquica, pasiva (ejecutada en el hilo de la persona que llama) e implementación activa (propio hilo en el que se ejecuta el fsm, los eventos se agregan a una cola).
Tomando el ejemplo de Juliets, la definición de la máquina de estados se vuelve muy fácil:
Actualización : la ubicación del proyecto se ha movido a: https://github.com/appccelerate/statemachine
fuente
Aquí hay un ejemplo de una máquina de estados finitos muy clásica, que modela un dispositivo electrónico muy simplificado (como un televisor)
fuente
private void DoNothing() {return;}
y reemplacé todas las instancias de null conthis.DoNothing
. Tiene el efecto secundario agradable de devolver el estado actual.States
toUnpowered, Standby, On
. Mi razonamiento es que si alguien me preguntara en qué estado está mi televisor, diría "Apagado" y no "Inicio". También cambiéStandbyWhenOn
yStandbyWhenOff
paraTurnOn
yTurnOff
. Eso hace que el código se lea de manera más intuitiva, pero me pregunto si hay convenciones u otros factores que hacen que mi terminología sea menos apropiada.Aquí hay una autopromoción descarada, pero hace un tiempo creé una biblioteca llamada YieldMachine que permite que una máquina de estado de complejidad limitada se describa de una manera muy limpia y simple. Por ejemplo, considere una lámpara:
Observe que esta máquina de estados tiene 2 disparadores y 3 estados. En el código de YieldMachine, escribimos un método único para todos los comportamientos relacionados con el estado, en el que cometemos la horrible atrocidad de usar
goto
para cada estado. Un activador se convierte en una propiedad o campo de tipoAction
, decorado con un atributo llamadoTrigger
. He comentado el código del primer estado y sus transiciones a continuación; Los siguientes estados siguen el mismo patrón.Corto y agradable, ¡eh!
Esta máquina de estados se controla simplemente enviándole disparadores:
Solo para aclarar, he agregado algunos comentarios al primer estado para ayudarlo a comprender cómo usar esto.
Esto funciona porque el compilador de C # realmente creó una máquina de estado internamente para cada método que utiliza
yield return
. Esta construcción generalmente se usa para crear secuencias de datos de manera perezosa, pero en este caso no estamos realmente interesados en la secuencia devuelta (que de todos modos es nula), sino en el comportamiento de estado que se crea bajo el capó.La
StateMachine
clase base reflexiona sobre la construcción para asignar código a cada[Trigger]
acción, lo que establece elTrigger
miembro y mueve la máquina de estado hacia adelante.Pero en realidad no necesitas entender las partes internas para poder usarlo.
fuente
goto
método entre métodos.goto
saltar entre métodos? No veo cómo podría funcionar eso. No,goto
es problemático porque da como resultado una programación procesal (esto en sí mismo complica cosas buenas como las pruebas unitarias), promueve la repetición del código (¿se dio cuenta de cómoInvalidTrigger
debe insertarse para cada estado?) Y finalmente hace que el programa fluya más difícil de seguir. Compare esto con (la mayoría) de otras soluciones en este hilo y verá que esta es la única en la que ocurre todo el FSM en un solo método. Eso suele ser suficiente para plantear una preocupación.goto
bastante bien.goto
saltar entre funciones, pero no admite funciones? :) Tienes razón, el comentario "más difícil de seguir" es más ungoto
problema general , de hecho no es un gran problema en este caso.Puede codificar un bloque iterador que le permite ejecutar un bloque de código de manera orquestada. La forma en que se divide el bloque de código realmente no tiene que corresponder a nada, es solo cómo desea codificarlo. Por ejemplo:
En este caso, cuando llamas a CountToTen, todavía no se ejecuta nada. Lo que obtienes es efectivamente un generador de máquina de estado, para el cual puedes crear una nueva instancia de la máquina de estado. Para ello, llame a GetEnumerator (). El IEnumerator resultante es efectivamente una máquina de estado que puede manejar llamando a MoveNext (...).
Por lo tanto, en este ejemplo, la primera vez que llame a MoveNext (...) verá "1" escrito en la consola, y la próxima vez que llame a MoveNext (...) verá 2, 3, 4 y luego 5, 6, 7 y luego 8, y luego 9, 10. Como puede ver, es un mecanismo útil para orquestar cómo deberían ocurrir las cosas.
fuente
Estoy publicando otra respuesta aquí, ya que se trata de máquinas de estado desde una perspectiva diferente; Muy visual.
Mi respuesta original es el código imperativo clásico. Creo que es bastante visual en cuanto al código debido a la matriz que simplifica la visualización de la máquina de estados. La desventaja es que tienes que escribir todo esto. La respuesta de Remos alivia el esfuerzo de escribir el código de la caldera, pero es mucho menos visual. Existe la tercera alternativa; Realmente dibujando la máquina de estado.
Si está usando .NET y puede apuntar a la versión 4 del tiempo de ejecución, entonces tiene la opción de usar las actividades de la máquina de estado del flujo de trabajo . En esencia, esto le permite dibujar la máquina de estado (como en el diagrama de Juliet ) y hacer que el tiempo de ejecución de WF lo ejecute por usted.
Consulte el artículo de MSDN Building State Machines with Windows Workflow Foundation para obtener más detalles y este sitio de CodePlex para obtener la última versión.
Esa es la opción que siempre preferiría cuando apunte a .NET porque es fácil de ver, cambiar y explicar a quienes no son programadores; ¡las imágenes valen más que mil palabras como dicen!
fuente
Es útil recordar que las máquinas de estado son una abstracción, y no necesita herramientas particulares para crear una, sin embargo, las herramientas pueden ser útiles.
Por ejemplo, puede realizar una máquina de estado con funciones:
Esta máquina buscaría gaviotas e intentaría golpearlas con globos de agua. Si falla, intentará disparar uno hasta que golpee (podría hacerlo con algunas expectativas realistas;)), de lo contrario, se regodeará en la consola. Continúa cazando hasta que se quedan sin gaviotas para acosar.
Cada función corresponde a cada estado; los estados de inicio y finalización (o aceptación ) no se muestran. Sin embargo, probablemente haya más estados allí que los modelados por las funciones. Por ejemplo, después de disparar el globo, la máquina está realmente en un estado diferente al que tenía antes, pero decidí que esta distinción no era práctica.
Una forma común es usar clases para representar estados y luego conectarlos de diferentes maneras.
fuente
Encontré este gran tutorial en línea y me ayudó a comprender las máquinas de estados finitos.
http://gamedevelopment.tutsplus.com/tutorials/finite-state-machines-theory-and-implementation--gamedev-11867
El tutorial es independiente del lenguaje, por lo que puede adaptarse fácilmente a sus necesidades de C #.
Además, el ejemplo utilizado (una hormiga buscando comida) es fácil de entender.
Del tutorial:
fuente
Hoy estoy profundamente en el patrón de diseño estatal. Lo hice y probé ThreadState, que equivale (+/-) a Threading en C #, como se describe en la imagen de Threading en C #
Puede agregar fácilmente nuevos estados, configurar movimientos de un estado a otro es muy fácil porque está encapsulado en la implementación del estado
Implementación y uso en: Implementa .NET ThreadState por patrón de diseño estatal
fuente
Todavía no he intentado implementar un FSM en C #, pero todo esto suena (o parece) muy complicado por la forma en que manejé los FSM en el pasado en lenguajes de bajo nivel como C o ASM.
Creo que el método que siempre he conocido se llama algo así como un "bucle iterativo". En él, esencialmente tiene un bucle 'while' que se cierra periódicamente en función de los eventos (interrupciones), luego vuelve al bucle principal nuevamente.
Dentro de los manejadores de interrupciones, pasaría un CurrentState y devolvería un NextState, que luego sobrescribe la variable CurrentState en el bucle principal. Hace esto ad infinitum hasta que el programa se cierra (o el microcontrolador se reinicia).
Lo que veo en otras respuestas parece muy complicado en comparación con cómo, en mi opinión, se pretende implementar un FSM; su belleza radica en su simplicidad y FSM puede ser muy complicado con muchos, muchos estados y transiciones, pero permiten que el proceso complicado se descomponga y digiera fácilmente.
Me doy cuenta de que mi respuesta no debería incluir otra pregunta, pero me veo obligado a preguntar: ¿por qué estas otras soluciones propuestas parecen ser tan complicadas?
Parecen ser como golpear un pequeño clavo con un martillo gigante.
fuente
Qué pelea StatePattern. ¿Se ajusta eso a tus necesidades?
Creo que está relacionado con el contexto, pero vale la pena intentarlo.
http://en.wikipedia.org/wiki/State_pattern
Esto permite que sus estados decidan a dónde ir y no la clase de "objeto".
Bruno
fuente
En mi opinión, una máquina de estados no solo está diseñada para cambiar estados, sino también (muy importante) para manejar desencadenantes / eventos dentro de un estado específico. Si desea comprender mejor el patrón de diseño de la máquina de estados, puede encontrar una buena descripción en el libro Head First Design Patterns, página 320 .
No se trata solo de los estados dentro de las variables, sino también de manejar los desencadenantes dentro de los diferentes estados. Gran capítulo (y no, no hay que pagar nada por mencionar esto :-) que contiene una explicación fácil de entender.
fuente
Acabo de contribuir con esto:
https://code.google.com/p/ysharp/source/browse/#svn%2Ftrunk%2FStateMachinesPoC
Este es uno de los ejemplos que muestran el envío directo e indirecto de comandos, con estados como IObserver (de señal), por lo tanto, responde a una fuente de señal, IObservable (de señal):
Nota: este ejemplo es bastante artificial y está destinado principalmente a demostrar una serie de características ortogonales. Rara vez debería existir una necesidad real de implementar el dominio de valor de estado en sí mismo mediante una clase completa, utilizando el CRTP (ver: http://en.wikipedia.org/wiki/Curiously_recurring_template_pattern ) como este.
Aquí hay un caso de uso de implementación ciertamente más simple y probablemente mucho más común (usando un tipo de enumeración simple como dominio de valor de estados), para la misma máquina de estados y con el mismo caso de prueba:
https://code.google.com/p/ysharp/source/browse/trunk/StateMachinesPoC/WatchingTVSample.cs
'HTH
fuente
StateChange
resuelve? A través de la reflexión? ¿Es eso realmente necesario?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 } }
Hice esta máquina de estado genérica con el código de Juliet. Me está funcionando de maravilla.
Estos son los beneficios:
TState
yTCommand
,TransitionResult<TState>
para tener más control sobre los resultados de salida de los[Try]GetNext()
métodosStateTransition
únicamente a través deAddTransition(TState, TCommand, TState)
lo que es más fácil trabajar con élCódigo:
Este es el tipo de retorno del método TryGetNext:
Cómo utilizar:
Así es como puede crear un a
OnlineDiscountStateMachine
partir de la clase genérica:Defina una enumeración
OnlineDiscountState
para sus estados y una enumeraciónOnlineDiscountCommand
para sus comandos.Definir una clase
OnlineDiscountStateMachine
derivada de la clase genérica usando esas dos enumeracionesDerive el constructor de
base(OnlineDiscountState.InitialState)
modo que el estado inicial se establezca enOnlineDiscountState.InitialState
Use
AddTransition
tantas veces como sea necesariousar la máquina de estado derivada
fuente
Creo que la máquina de estados propuesta por Juliet tiene un error: el método GetHashCode puede devolver el mismo código hash para dos transiciones diferentes, por ejemplo:
Para evitar este error, el método debería ser así:
Alex
fuente
int
valores posibles ). Es por esoHashCode
que siempre se implementa junto conEquals
. Si los códigos hash son los mismos, se verifica la equidad exacta de los objetos utilizando elEquals
métodoFiniteStateMachine es una máquina de estado simple, escrita en C # Link
Ventajas de usar mi biblioteca FiniteStateMachine:
Descargar DLL Descargar
Ejemplo en LINQPad:
fuente
Yo recomendaría state.cs . Personalmente utilicé state.js (la versión de JavaScript) y estoy muy contento con él. Esa versión de C # funciona de manera similar.
Instancias de estados:
Instancias algunas transiciones:
Define acciones en estados y transiciones:
Y eso es todo. Mira el sitio web para más información.
fuente
Hay 2 paquetes populares de máquinas de estado en NuGet.
Appccelerate.StateMachine (13.6K descargas + 3.82K de la versión heredada (bbv.Common.StateMachine))
StateMachineToolkit (1.56K descargas)
Appccelerate lib tiene buena documentación , pero no es compatible con .NET 4, así que elegí StateMachineToolkit para mi proyecto.
fuente
Otra alternativa en este repositorio https://github.com/lingkodsoft/StateBliss utilizó una sintaxis fluida, admite disparadores.
fuente
Puedes usar mi solución, esta es la forma más conveniente. También es gratis.
Crear máquina de estados en tres pasos:
1. Cree un esquema en el editor de nodos🔗 y cárguelo en su proyecto usando biblioteca📚
2. Describe la lógica de tu aplicación en eventos⚡
3. Ejecute la máquina de estados🚘
Enlaces:
Editor de nodos: https://github.com/SimpleStateMachine/SimpleStateMachineNodeEditor
Biblioteca: https://github.com/SimpleStateMachine/SimpleStateMachineLibrary
fuente