Implementación de máquina de estados finita (FSM) con Java 8

Publicado por pico.dev el , actualizado el .
blog-stack java planeta-codigo programacion
Comentarios

Es raro pero no he encontrado una librería adecuada en Java con una implementación de una máquina de estados. Stateless4j puede ser una candidata pero también tiene algunas deficiencias que pueden hacer que no nos sirva. Basándome en Stateless4j y usando Java 8 he creado una implementación de FSM con una funcionalidad similar y más ligera donde una única instancia de la máquina de estados es independiente del número de instancias de objetos en las que se use.

Nota: Cuando busqué no encontré pero resulta que entre uno de los numerosos subproyectos de Spring está uno que sirve como implementación de máquina de estados, Spring Statemachine. Por supuesto, Spring Statemachine es mucho más avanzado que este ejemplo que muestro en el artículo y lo recomiendo también por su mejor soporte en futuras actualizaciones.
Java

Hace un par de años escribía un artículo sobre cómo implementar una máquina de estados usando el patrón de diseño State. El patrón de diseño State y el ejemplo era válido sin embargo podía tener algunas deficiencias. Una de ellas es que necesitaba una clase por cada estado diferente, si los estados son una docena el número de archivos necesarios son altos. Por otro lado cada estado debe implementar todas las posibles transiciones o métodos de la interfaz del estado que también pueden ser altos dependiendo del numero de estados y transiciones que se haga en ellos, aunque con la clase abstracta AbstractCompraState del estado solo necesitamos implementar los métodos de transiciones propias del estado. Se podría añadir pero el ejemplo del patrón de diseño estado no tiene operaciones para saber si una determinada transición u operación puede realizarse y en el caso de añadir esa funcionalidad si tuviésemos varias máquinas de estados probablemente duplicaríamos parte del código en cada una de ellas. También viendo el código el flujo de estados no es muy obvio. Por todo ello en este artículo comentaré otra posibilidad, creo que mejor, que es implementando una máquina de estados finita (FSM, Finite State Machine) y usando Java 8 aprovechando sus nuevas características como los streams e interfaces funcionales.

Me ha parecido raro pero no he encontrado muchas librerías en Java que implementen una máquina de estados, la mejor que he visto ha sido Stateless4j. Es perfectamente usable, sin embargo, al hacer un ejemplo me he dado cuenta de que también tiene un defecto importante. Y es que si queremos aplicar una máquina de estados a una instancia de cierta clase, Stateless4j necesita una instancia de la máquina de estados por cada instancia de esa clase, puede ser usado para controlar, por ejemplo, el estado de unos cuantos personajes de un juego o del juego mismo pero si tenemos unos cuantas miles de instancias como puede ser en una aplicación de gestión el código será poco eficiente y el consumo de memoria mayor. Además usa Java 7 y con Java 8 algunas cosas son más fáciles y claras.

Basándome en Stateless4j he creado una nueva implementación y usando Java 8 la tarea ha sido más sencilla y potente. Las funcionalidades que posee la implementación de esta máquina de estados son:

  • Definir estados.
  • Definir transiciones, manejadores de transiciones y opcionalmente condiciones que se deben dar para realizar el cambio de estado.
  • Definir manejadores de entrada y salida por transición.
  • Conocer el estado actual y si se está en un determinado estado.
  • Conocer los eventos aceptados para cambiar de estado.
  • Provocar eventos en la máquina de estados proporcionando un objeto y unos datos con información adicional para procesar el evento.
  • Definir manejadores para excepciones al realizar alguna transición o intentos de transiciones cuando no hay manejador definido.

La API de la máquina de estados se compone de dos builders que proporcionan una API fluida, uno para crear los estados (StateBuilder) y otro para la máquina de estados (StateMachineBuilder). Además de la máquina de estados (StateMachine) y la clase que representa un estado (State).

Internamente se usa la clase TransitionBehiavour que define el comportamiento en una transición y ante un evento. Si posee una función de protección (guard) se comprueba antes de ejecutar la acción (selector) y que devolverá el nuevo estado.

El siguiente es un ejemplo de uso similar al del artículo del patrón de diseño State con un hipotético flujo de estados para una compra junto con el código de la máquina de estados necesario para implementarlo y con unas pruebas unitarias. El enumerado State define los posibles estados y el enumerado Trigger define los posibles eventos, en constructor static se define la única instancia de máquina de estados necesaria para manejar cualquier número de instancias de Purchase usando las clases builder.

La interfaz Subject proporciona las operaciones para que la máquina de estados pueda obtener y modificar el estado del objeto manejado en una transición, en este caso de una instancia de Purchase.

Otra posibilidad a las máquinas de estados son las herramientas de procesos de negocio o BPM (Business Process Management) pero salvo que tengamos algo muy complejo la máquina de estados de este ejemplo será más que suficiente para la mayoría de situaciones. Hace un tiempo escribir varios artículos sobre Activiti y Drools:

El código fuente completo está disponible en mi repositorio de ejemplos en GitHub.