Implementación de una máquina de estados

2
18524

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:

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.

2 COMENTARIOS

  1. 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,

DEJA UNA RESPUESTA

Por favor ingrese su comentario!

He leído y acepto la política de privacidad

Por favor ingrese su nombre aquí

Información básica acerca de la protección de datos

  • Responsable:
  • Finalidad:
  • Legitimación:
  • Destinatarios:
  • Derechos:
  • Más información: Puedes ampliar información acerca de la protección de datos en el siguiente enlace:política de privacidad