sábado, 28 de febrero de 2015

Patrón Estado (State Pattern)

Fuentes:
Patrones de comportamiento (V). Patrón State, por Daniel García
State Design Pattern, por Sourcemaking

Las cosas cambian como respuesta a diversas fuerzas, internas y externas, por ejemplo, muchas cosas cambian de estado a lo largo de un proceso en el que unas fases suceden a otras en un orden determinado.
A veces tenemos que modelar esos cambios de estado de una entidad en una aplicación. El paso de un estado a otro lo denominamos transición y es un proceso en el que pueden modificarse varias cosas en la propia entidad o en entidades relacionadas. Por ejemplo, en un sistema de gestión de biblioteca, la devolución de un libro prestado marcaría su estado como disponible, pero también retiraría el libro de la lista de préstamos pendientes de devolución de un lector y, tal vez, notificaría otro lector apuntado en una lista de espera por ese libro.
El patrón Estado (State Pattern) es útil para manejar situaciones en las que una clase puede tener diferentes estados y debe gestionar varias acciones para realizar las transiciones entre ellas. Si los cambios de estado requieren algo más que modificar el campo de estado de la clase, como podría ser aplicar ciertos cambios a otras clases o cualquier otra acción, entonces es señal de que necesitas el patrón Estado.

La situación

El patrón Estado se aplica cuando una clase tiene que gestionar estados y las correspondientes transiciones entre ellas.
Por ejemplo, un sistema de publicación (como un gestor de contenidos) en el que los artículos pueden tener estados como borrador, listo para revisar, publicado y retirado. Otro ejemplo es un sistema de incidencias de mantenimiento, en el que cada incidencia puede tener estados como abierta, asignada, en evaluación o cerrada.

El patrón

El patrón Estado encapsula los posibles estados de la entidad como clases y hace que una clase contexto delegue la realización de las transiciones en las clases de estado.
En este patrón se crea una clase de estado por cada estado que puede tomar la entidad. Cada una de estas clases implementa una interfaz que declara tantos métodos como transiciones se deban gestionar.
Cada clase implementa aquellas transiciones que tienen sentido para el estado que representan y devuelve una excepción o no hace nada si la transición no es pertinente en ese caso.
Retomando el ejemplo del sistema de publicación, desde el estado borrador podemos pasar a listo para revisar o publicar (por ejemplo si el autor tiene privilegios para publicar sin ser revisado por un editor), pero no tiene sentido pasar a retirado. Por otro lado, desde el estado publicar podríamos pasar a retirado, pero no tiene sentido pasar a listo para revisar. Evidentemente, esto depende de cómo se hayan definido las reglas de control editorial para el caso concreto, lo importante es darse cuenta de que cada clase de estado debe implementar todas las transiciones posibles en el sistema.
La clase de contexto implementará también los métodos para cada cada transición, pero en este caso lo que hará será delegar en una clase de Estado. Por supuesto, necesitará instanciar una clase Estado a partir de la propiedad de estado de la entidad que le toque manejar.

Beneficios

Este patrón elimina la necesidad de que la clase base contenga métodos con estructuras de tipo switch o múltiples if-then, que deben ser cambiados si en un momento dado necesitamos gestionar nuevos estados o modificar las operaciones que se realizan durante la transición.
Si cambian los requisitos de las transiciones modificaremos los métodos de las clases de estado. Si necesitamos gestionar nuevos estados crearemos nuevas clases de Estado.

Cómo realizarlo

Diseño

Para aplicar el patrón Estado debemos identificar primero los estados y las transiciones entre ellos. Lo más práctico es hacer un diagrama de máquina de estados en el que representamos los estados como círculos y las transiciones como flechas que nos llevan de uno a otro.
Ponemos un nombre a cada estado (normalmente un adjetivo) y un nombre a cada tipo de transición (normalmente un verbo). Estos nombres deberían indicar con claridad el estado (borrador, publicado) y la intención de la transición (publicar, retirar).

Implementación

Estoy experimentando con varias implementaciones del patrón State aunque no me ajusto estrictamente a la definición. De momento, esta es la que más me ha convencido:
Crear una interfaz State (por ejemplo, PostState) en la que declaro los métodos para las transiciones (publicar, revisar, retirar). Las clases de Estado implementarán esta interfaz, así como la clase base. Los métodos toman como parámetro una instancia de la clase base.
Crear una clase abstracta que implemente la interfaz anterior. Los métodos arrojarán una Excepción (yo he creado una StateException). Lo hago así para tener un comportamiento por defecto en las clases de estado concretas en las que, de este modo, sólo tengo que implementar los métodos necesarios para las transiciones aplicables desde un estado. (Puedes ver aquí tanto la interfaz como la clase abstracta)
Crear las clases para cada estado extendiendo la clase abstracta e implementando los métodos de las transiciones que sean adecuadas para cada estado. Como he dicho antes, la clase abstracta me proporciona un comportamiento por defecto, como puede ser arrojar una excepción o bien no hacer nada. De todos modos, estoy procurando que desde la interfaz de usuario no haya opción para realizar transiciones inválidas.
Adicionalmente, he creado una clase factoría abstracta para instanciar objetos de Estado a partir de un par de claves. Se configura pasándole tres parámetros: una clave para localizar las clases de estado, el valor de estado y el nombre de la clase que se deberá instanciar. Para obtener un objeto, se pasa la clave y el valor.
Crear o identificar una clase de contexto que contenga a la clase base y que implemente una interfaz similar a la ya definida State (un método por transición) en la que obtenga una instancia de la clase de Estado correspondiente y ejecute la transición solicitada. En mi caso he aprovechado el Controlador en el que añado las acciones correspondientes a cada una de las transiciones, que en este caso van a ser llamadas vía Ajax (aquí un ejemplo).