- State (patrón de diseño)
-
El patrón de diseño State se utiliza cuando el comportamiento de un objeto cambia dependiendo del estado del mismo. Por ejemplo: una alarma puede tener diferentes estados, como desactivada, activada, en configuración. Definimos una interfaz Estado_Alarma, y luego definimos los diferentes estados.
Contenido
Introducción
En determinadas ocasiones, cuando el contexto en el que se está desarrollando requiere que un objeto tenga diferentes comportamientos según el estado en que se encuentra, resulta complicado poder manejar el cambio de comportamientos y los estados de dicho objeto, todos dentro del mismo bloque de código. El patrón State propone una solución a esta complicación, creando básicamente, un objeto por cada estado posible del objeto que lo llama.
Propósito
Permite a un objeto alterar su comportamiento según el estado interno en que se encuentre.
Motivación
El patrón State está motivado por aquellos objetos en que, según su estado actual, varía su comportamiento ante los diferentes mensajes. Como ejemplo se toma una clase TCPConection que representa una conexión de red, un objeto de esta clase tendrá diferentes respuestas según su estado (Listening, Close o Established). Por ejemplo la invocación al método Open de un objeto de la clase TCPConection diferirá su comportamiento si la conexión se encuentra en Close o en Established.
Problema
Existe una extrema complejidad en el código cuando se intenta administrar comportamientos diferentes según una cantidad de estados diferentes. Asimismo el mantenimiento de este código se torna dificultoso, e incluso se puede llegar en algunos casos puntuales a la incongruencia de estados actuales por la forma de implementación de los diferentes estados en el código (por ejemplo con variables para cada estado).
Consideraciones
Se debe contemplar la complejidad comparada con otras soluciones.
Solución
Se implementa una clase para cada estado diferente del objeto y el desarrollo de cada método según un estado determinado. El objeto de la clase a la que le pertenecen dichos estados resuelve los distintos comportamientos según su estado, con instancias de dichas clases de estado. Así, siempre tiene presente en un objeto el estado actual y se comunica con este para resolver sus responsabilidades.
La idea principal en el patrón State es introducir una clase abstracta TCPState que representa los estados de la conexión de red y una interfaz para todas las clases que representan los estados propiamente dichos. Por ejemplo la clase TCPEstablished y la TCPClose implementan responsabilidades particulares para los estados establecido y cerrado respectivamente del objeto TCPConnection. La clase TCPConnection mantiene una instancia de alguna subclase de TCPState con el atributo state representando el estado actual de la conexión. En la implementación de los métodos de TCPConnection habrá llamadas a estos objetos representados por el atributo state para la ejecución de las responsabilidades, así según el estado en que se encuentre, enviará estas llamadas a un objeto u otro de las subclases de TCPState.
Estructura UML
Participantes
- Context(Contexto): Este integrante define la interfaz con el cliente. Mantiene una instancia de ConcreteState (Estado Concreto) que define su estado actual
- State (Estado):Define una interfaz para el encapsulamiento de la responsabilidades asociadas con un estado particular de Context.
- Subclase ConcreteState:Cada una de estas subclases implementa el comportamiento o responsabilidad de Context.
El Contexto (Context) delega el estado específico al objeto ConcreteState actual Un objeto Context puede pasarse a sí mismo como parámetro hacia un objeto State. De esta manera la clase State puede acceder al contexto si fuese necesario. Context es la interfaz principal para el cliente. El cliente puede configurar un contexto con los objetos State. Una vez hecho esto, los clientes no tendrán que tratar con los objetos State directamente. Tanto el objeto Context como los objetos de ConcreteState pueden decidir el cambio de estado.
Colaboraciones
El patrón State puede utilizar el patrón Singleton cuando requiera controlar que exista una sola instancia de cada estado. Lo puede utilizar cuando se comparten los objetos como Flyweight existiendo una sola instancia de cada estado y ésta es compartida con más de un objeto.
¿Cómo funciona?
La clase Context envía mensajes a los objetos ConcreteState dentro de su código para brindarle a estos la responsabilidad que debe cumplir el objeto Context. Así el objeto Context va cambiando las responsabilidades según el estado en que se encuentra, puesto que también cambia de objeto ConcreteState al hacer dicho cambio de estado.
¿Cuándo emplearlo?
Esta apuntado a cuando un determinado objeto tiene diferentes estados y también distintas responsabilidades según el estado en que se encuentre en determinado instante. También puede utilizarse para simplificar casos en los que se tiene un complicado y extenso código de decisión que depende del estado del objeto
Ventajas y desventajas
Se encuentran las siguientes ventajas:
- Se localizan fácilmente las responsabilidades de los estados específicos, dado que se encuentran en las clases que corresponden a cada estado. Esto brinda una mayor claridad en el desarrollo y el mantenimiento posterior. Esta facilidad la brinda el hecho que los diferentes estados están representados por un único atributo (state) y no envueltos en diferentes variables y grandes condicionales.
- Hace los cambios de estado explícitos puesto que en otros tipos de implementación los estados se cambian modificando valores en variables, mientras que aquí al estar representado cada estado.
- Los objetos State pueden ser compartidos si no contienen variables de instancia, esto se puede lograr si el estado que representan esta enteramente codificado en su tipo. Cuando se hace esto estos estados son Flyweights sin estado intrínseco.
- Facilita la ampliación de estados
- Permite a un objeto cambiar de clase en tiempo de ejecución dado que al cambiar sus responsabilidades por las de otro objeto de otra clase la herencia y responsabilidades del primero han cambiado por las del segundo.
Se encuentran la siguiente desventaja:
- Se incrementa el número de subclases.
public class Test { public static void main( String arg[] ) { try { State state = new ConcreteStateA(); Context context = new Context(); context.setState( state ); context.request(); } catch( Exception e ) { e.printStackTrace(); } } }
public class Context { private State state; public void setState( State state ) { this.state = state; } public State getState() { return state; } public void request() { state.handle(); } }
public interface State { void handle(); }
public class ConcreteStateA implements State { public void handle() { } } public class ConcreteStateB implements State { public void handle() { } }
/** * State patter:We have a specific class(Context) that manages the state changes of a external class by creating different instance depending * on the state you want to adopt. * Every class that you create implements an interface(State) that define the method name that they have to implement * @author Pperez * */ public class StatePattern { public void main(String args[]){ try{ State state; Context context = new Context(); SocketChannel socketChannel = null; //-----------------------------\\ // OPEN/LISTENING SOCKET \\ //-----------------------------\\ //First State: state = new ConnectSocketState(socketChannel); context.setState( state ); socketChannel = context.request(); //-----------------------------\\ // CLOSE SOCKET \\ //-----------------------------\\ //Second State: state = new CloseSocketState(socketChannel); context.setState( state ); socketChannel = context.request(); }catch( Exception e ) { e.printStackTrace(); } } public class Context { private State state; public void setState( State state ) { this.state = state; } public State getState() { return state; } public SocketChannel request() { return state.processState(); } } public interface State { SocketChannel processState(); } public class ConnectSocketState implements State { SocketChannel socketChannel; public ConnectSocketState(SocketChannel socketChannel){ this.socketChannel=socketChannel; } public SocketChannel processState() { try { int port = 21; InetAddress host = InetAddress.getByName("192.168.1.1"); SocketAddress adress = new InetSocketAddress(host, port); socketChannel = SocketChannel.open(adress); socketChannel.configureBlocking(true); } catch (IOException e) { e.printStackTrace(); } return socketChannel; } } public class CloseSocketState implements State { SocketChannel socketChannel; public CloseSocketState(SocketChannel socketChannel){ this.socketChannel=socketChannel; } public SocketChannel processState(){ try { socketChannel.close(); } catch (IOException e) { e.printStackTrace(); } return socketChannel; } } }
Conclusiones
El patrón no indica exactamente dónde definir las transiciones de un estado a otro. Existen dos formas de solucionar esto: Una es definiendo estas transiciones dentro de la clase contexto, la otra es definiendo estas transiciones en las subclases de State. Es más conveniente utilizar la primera solución cuando el criterio a aplicar es fijo, es decir, no se modificará. En cambio la segunda resulta conveniente cuando este criterio es dinámico, el inconveniente aquí se presenta en la dependencia de código entre las subclases.
También hay que evaluar en la implementación cuándo crear instancias de estado concreto distintas o utilizar la misma instancia compartida. Esto dependerá si el cambio de estado es menos frecuente o más frecuente respectivamente.
Categoría:- Patrones de diseño
Wikimedia foundation. 2010.