Desde que empecé a codificar el ejemplo que acompaña a este post, he tratado de recordar algún proyecto importante donde no se requiriera el manejo de estados en algún punto del sistema. Lo cierto es que cuando debemos programar un escenario donde los estados de algún componente, deben ser controlados para poder determinar la lógica a seguir, siempre resulta complejo organizarlos y determinar la secuencia correcta. Sin embargo, lo más complejo no reside en determinar esa secuencia, sino en controlar que la secuencia se aplique adecuadamente, considerando sus desviaciones.
Lo cierto es que al igual que con los otros patrones, no podemos creer que los patrones pueden solucionarnos todos nuestros problemas. Recuerden el famoso patrón HAMMER, este patrón consta de tres pasos: AJA, WOW, OHOH. El “aja” cuando descubrimos los patrones y creemos que hemos encontrado la solución a todo; el “wow” conforme vamos aprendiendo a usarlos y por último el “ohoh” cuando nos damos cuenta que no en todos los escenarios se puede aplicar un patrón ;-).
Es en el último paso que empezamos a descubrir modificaciones, adaptaciones o alguna desviación del patrón, que alguien ha hecho, basado en el original.
State Pattern
Este patrón se emplea para que un objeto, pueda modificar su comportamiento según un estado interno y determinar si se puede o no ejecutar alguna acción. Lo que aparenta que su comportamiento ha cambiado. Simplifica nuestro código sobre todo cuando los estados deben cambiar dependiendo de un estado previo. Por ejemplo A->B->C->D y no es posible pasar a C sin pasar por B.
El patrón requiere de tres elementos importantes:
- Contexto: Esta clase toda la implementación y se presenta como la única interface hacia el mundo exterior.
- EstadoBase: Esta clase es la que se debe usar para la derivación de todos los estados.
- EstadosConcretos(A,B,C): Son los diferentes estados.
Requerimientos
Nuestra aplicación requiere controlar el estado de un componente que se mueve dentro de un ciclo siendo el inicia en inactivo, después pasara al estado activo, podrá cambiar al estado de ocupado y cuando termine, se pondrá en Disponible, para volver a Ocupado si lo volviéramos a llamar. Sin embargo debe poder resetearse.
Análisis
La solución inmediata que se nos viene a la mente, seria usar CASE o IF, usando una variable numérica, donde el numero vaya incrementando, para presentar los diferentes estados y una variable de texto donde pondríamos el estado actual. Por ejemplo:
Solución
Aun que nos parece simple, por cuestiones didácticas, vamos a separar el control de los estados y la transición entre ellos; o sea, abstraer y encapsular la lógica de transición, para que la implementación sea independiente de lo que representan. La única responsabilidad de la clase deberá ser saber a cual estado podrá pasar, según un estado previo. Los cambios de estado estarán ocultos a clases externas. Nuestra solución será una variante del patrón original.
Manos a la obra
Lo primero será crear nuestra ya sabida interface:
Para evitarnos las variables numéricas y las de texto en la clase abstracta, he preferido usar un Enum, para cada estado: Active, InActive, Bussy, Free. Por lo que quedaría de esta forma:
Para encapsular cierta lógica que debería ser implementada en cada instancia creamos una clase abstracta que implemente la interface:
Una vez que hemos codificado esta clase, podemos empezar a crear las clases que deriven de esta; una para cada estado: Active, InActive, Bussy, Free.
Instrucciones: Lo único que se debe hacer en cada clase es escribir el código del método SetState, el cual establece la propiedad correspondiente a la clase. Y el constructor que llama al método que establece el estado de la clase:
Por simplificar el ejemplo, no les agrego la implementación de los demás, solo sigan las instrucciones.
En este momento podemos escribir el código de nuestra clase contexto. Para simplificar el código separe la lógica de transición de los estados en una clase aparte, por lo que en esta solo se exponen un constructor default, dos métodos públicos y una propiedad de solo lectura:
La clase que realiza la lógica de transición se implemento usando el patrón Singleton y expone un método por medio del cual se realiza el cambio de estado: Nuestro código de pruebas en la consola seria:
El cual presentara lo siguiente:
La única observación es como regresa al primer estado cuando usamos Reset, y antes de usarlo, se mantenía entre Free y Bussy.
Comentario final: Aun que esta implementación no es la tradicional del patrón, nos permite acercarnos a el y ver un uso de la vida real. En un post futuro les presentare una implementación mas apegada al patrón.
El poder del código solo es completo, si tenemos el conocimiento de como usarlo.
No hay comentarios:
Publicar un comentario