Implementación de una máquina de estados.
0. Índice de contenidos.
1. Introducción
Según wikipedia:
«Se denomina máquina de estados a un modelo de comportamiento de un sistema con entradas y salidas, en donde las salidas dependen no sólo de las señales de entradas actuales sino también de las anteriores. Las máquinas de estados se definen como un conjunto de estados que sirve de intermediario en esta relación de entradas y salidas, haciendo que el historial de señales de entrada determine, para cada instante, un estado para la máquina, de forma tal que la salida depende únicamente del estado y las entradas actuales.»
2. Entorno
El tutorial está escrito usando el siguiente entorno:
- Hardware: MacBook Pro 15′ (2.8 GHz Intel Core 2 Duo, 4GB DDR3 SDRAM).
- Sistema Operativo: Mac OS X Snow Leopard 10.6.3.
- NVIDIA GeForce 9600M GT 512Mb.
- Toshiba 500 Gb. 5400r.p.m.
3. Diseño del modelo.
Una vez tenemos una definición conceptual veamos que tendremos en cuanta a la hora de diseñar nuestra máquina de estados.
Nuestro modelo debe permitir incluir nuevos estados y transiciones sin necesidad de modificar los ya existentes.
Las transiciones estarán declaradas en el estado origen.
4. Implementación.
State
Clase abstracta que aporta la funcionalidad común a todos los estados.
En la implementación específica de cada estado tenemos que definir las transiciones.
(desde este estado a que estado mediante que evento)
El método fire nos permite evaluar el evento lanzado para decidir a que estado debemos ir, ante un evento devuelve el estado destino.
Si la transición es nula significa que estamos lanzando un evento que no provoca transición, por tanto debemos permanecer en el propio estado.
El método getServiceName() nos permite obtener de cada estado su nombre (en este caso se corresponde con su nombre de clase en minúscula).
package com.autentia.stateMachine; import java.util.HashMap; import java.util.Map; public abstract class State { protected final Map<String, Transition> transitionByEvent = new HashMap<String, Transition>(); public State fire(StateEvent event) { final Transition transition = transitionByEvent.get(event.getClass().getName()); if (transition == null) { return this; } return transition.nextState(event); } @Override public String toString() { return this.getClass().getName(); } public String getServiceName(){ return this.getClass().getSimpleName().toLowerCase(); } }
DefaultState
Anotado como @Service delegamos su gestión a Spring (los maneja como Singleton).
Este estado inicial extiende al básico definiendo sus transiciones en el método postConstruct.
Este añade al mapa los pares: clave -> evento esperado, valor -> transición del estado origen al estado destino.
Por cada implementación, Spring nos genera una instancia inmutable que comparten todas las instancias de aquellos objetos que tengan estado.
package com.autentia.stateMachine; import javax.annotation.PostConstruct; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @Service public class DefaultState extends State { private State secondState; private State thirdState; @Autowired public void setSecondState(State secondState) { this.secondState = secondState; } @Autowired public void setThirdState(State thirdState) { this.thirdState = thirdState; } @PostConstruct void postConstruct() { transitionByEvent.put(FirstToSecondByEvent.class.getName(), new Transition(this, secondState)); transitionByEvent.put(FirstToThirdByEvent.class.getName(), new Transition(this, thirdState)); } }
Transition
Esta clase representa el par estado actual estado siguiente que define una transición.
Se almacenan en el mapa que construye cada estado en su metodo postConstruct.
package com.autentia.stateMachine; public class Transition { private final State sourceState; private final State targetState; public Transition(State sourceState, State targetState) { this.sourceState = sourceState; this.targetState = targetState; } public State nextState(StateEvent event) { return targetState; } }
StateEvent
Esta sencilla clase representa un evento que genera una transición para un estado concreto.
package com.autentia.stateMachine; public class StateEvent { private Object value; public StateEvent() { value = null; } public StateEvent(Object value) { this.value = value; } public Object getValue() { return value; } }
FirstToSecondByEvent
Esta clase representa un evento específico para el estado que hemos definido previamente.
Estos eventos serán lanzados a través del fire del estado actual para provocar la transición pertinente.
package com.autentia.stateMachine; public class FirstToSecondByEvent extends StateEvent { // La clase solo representa el evento. }
5. Conclusión.
Como vemos este modelo nos permite ir añadiendo estados y eventos de forma muy sencilla, teniendo únicamente que implementar la clase que define las transiciones y los eventos que las lanzan.
Nos hemos ayudado de Spring para la gestión de los estados como Singleton y que todas las instancias de objetos con estado compartan la máquina de estados sin necesidad de ir creandolos cada vez que provocamos en cambio.
Espero que os haya sido de utilidad.
Cualquier aclaración, duda o sugerencia podéis incluirla en la zona de comentarios.
Un saludo.
Alvaro Cuesta.
Buenas,
Por favor, ¿Podrías indicar cómo sería la primera llamada para indicar el primer estado?
Gracias. Saludos,
Buenas,
He adaptado su ejemplo a la implementación que tenía que hacer similar. Ahora bien, ¿podría poner un ejemplo de la transición de estados y la consulta fire?
Gracias. Saludos,