State (patrón de deseño)
O patrón de deseño State (Estado), utilízase en situacións nas que un obxecto adopta un comportamento diferente cada vez que cambia o seu estado interno.
Índice |
Introdución [editar]
O patrón State (Estado) é un patrón de comportamento, que se aplica cando un obxecto ten a necesidade de comportarse dun xeito distinto dependendo do estado no que se encontre, e este estado pode cambiar durante a vida do obxecto.
Normalmente os cambios de comportamento e os estados, adoitan ser complicados de manexar dentro do mesmo obxecto. A solución proposta por este patrón, consiste en crear un obxecto por cada estado posible. Cada vez que o obxecto cambia de estado, semella que este cambia a súa clase.
Tamén é coñecido como Objects for States (Estados como Obxectos).
Propósito [editar]
Permite que un obxecto modifique o seu comportamento cando cambia o seu estado interno.
Motivación [editar]
Partimos da necesidade de que un obxecto adoite un comportamento distinto dependendo do estado no que se encontre.
Nesta situación, teríamos a posibilidade de centrar toda a responsabilidade nunha única clase, na que, a través de condicións (switch) o obxecto actuara dun xeito diferente en base ó estado no que se encontrara.
Esta solución implicaría unha implementación cun código complexo e cun mantemento custoso. Ademais normalmente o obxecto terá atributos propios de cada estado en concreto (que non terán sentido se o obxecto se encontra noutro estado). Por tanto nalgunhas situacións poderíamos chegar a incongruencias de estados.
Solución [editar]
A solución proposta por este patrón, consiste en:
- Introducir unha clase abstracta que representa os estados: Esta superclase define a interface dos métodos que dependen do estado.
- Crear unha subclase para cada estado: Cada unha destas subclases implementarán o comportamento específico do estado concreto.
Estrutura [editar]
Participantes [editar]
- Contexto (Contexto): Define a interface para os clientes. Mantén unha instancia dun estado concreto que define o estado actual do obxecto contexto.
- Estado (State): Define unha interface para encapsular o comportamento asociado a un estado concreto do contexto.
- EstadoConcreto (ConcreteState): Cada unha destas subclases implementa o comportamento asociado cun estado do contexto.
Colaboracións [editar]
- O contexto delega os comportamentos específicos do estado ao obxecto do estado concreto.
- O contexto pode pasarse como argumento a el mesmo ao obxecto estado. Deste xeito o obxecto estado pode acceder á información do contexto.
- A configuración dun contexto pode realizala un cliente con obxectos estado.
- Os clientes utilizan o contexto como interface, sen necesidade de manexar obxectos estado (EstadoConcreto) directamente.
- Tanto o contexto como os estados concretos teñen a posibilidade de cambiar o estado actual do obxecto contexto.
- Cando só é preciso ter unha única instancia de cada estado adoitase utilizar o patrón Singleton. Isto é apropiado por exemplo cando se comparten os obxectos como Flyweights existindo unha soa instancia de cada estado (EstadoConcreto) que é compartida con máis dun obxecto Contexto.
Aplicabilidade [editar]
É recomendado utilizar este patrón nas seguintes situacións:
- O comportamento dun obxecto depende do seu estado, e debe cambiar en tempo de execución dependendo dese estado.
- As operacións teñen largas sentencias condicionais con múltiples ramas que dependen o estado do obxecto. Este estado adoitase representar por uunha ou máis constantes numeradas. Coa aplicación do patron, cada rama destas condicións estará nunha clase separada. Isto permite tratar o estado do obxecto como un obxecto independente e xestionar a súa vida independentemente de outros obxectos.
Consecuencias da aplicación do patrón [editar]
A aplicación deste patrón ten as seguintes consecuencias:
Vantaxes [editar]
Entre as vantaxes habituais encóntranse as seguintes:
-
- Localiza e separa o comportamento específico de cada estado: Será doado localizar as responsabilidades de cada estado, xa que o comportamento de cada estado está nunha clase separada (EstadoConcreto).
- Facilita a incorporación de novos estados e transicións: Para incorporar un novo estado soamente será necesario crear unha nova subclase (EstadoConcreto). Os diferentes estados están representados por unha asociación, state (_estado no diagrama do apartado Estrutura ). Fronte á outra opción comentada no apartado Motivación, na que normalmente precisaríase revisar a implementación anterior (e isto pode ser complexo xa que pode contar cun alto número de condicións).
- Maior claridade no desenvolvemento e mantemento posterior menos custoso: Unha consecuencia dos dous puntos anteriores.
- As transicións entre estados serán explícitas: Relacionado co comentado na segunda vantaxe.
- Nalgunhas circunstancias é posible a compartición de obxectos estado: Se os estados non posúen variables de instancia, entón varios obxectos Contexto poden compartir o mesmo estado, polo que so será necesaria a existencia dunha única instancia de cada estado (EstadoConcreto). Nesta situación os estados non contarán con estado intrínseco, senón só con comportamento (Flyweights).
- Permite ao obxecto cambiar de clase en tempo de execución: Xa que ao cambiar as súas responsabilidades polas doutro obxecto doutra clase, a herdanza e responsabilidades do primeiro cambiarán polas do segundo.
Desvantaxes [editar]
Pola contra, teremos a seguinte desvantaxe:
- O deseño será menos compacto: Xa que aumenta o número de clases.
Implementación [editar]
Transicións entre estados [editar]
O patrón non especifica que participante define os criterios para as transicións entre estados. Se os criterios son fixos, poden implementarse no Contexto. Nembergantes, adoita ser mais flexible e convinte que sexan as propias subclases de estado (EstadoConcreto) as que especifiquen o seu estado sucesor e cando realizar a transición. Isto require engadir unha interface ao Contexto que permita aos obxectos estado asignar o estado actual do Contexto. Descentralizar deste xeito a lóxica de transición facilita modificar ou estender dita lóxica definindo novas subclases estado. Unha desvantaxe desta citada descentralización é que a subclase estado coñecerá cando menos a outra subclase estado (EstadoConcreto) polo que existirá unha dependencia de implementación entre estas subclases.
Creación e destrución dos obxectos estado [editar]
Na implementación debemos decidir cando crear os obxectos estado (EstadoConcreto), teremos dúas opcións:
- Crealos só cando se precisen e destruílos despois.
- Crealos ao principio e non destruílos nunca.
A primeira opción é preferible cando non se coñecen os estados en tempo de execución e os contextos cambian de estado con pouca frecuencia. Deste xeito evítase a creación de obxectos innecesarios, esto pode ser moi importante cando os obxectos estado teñen un gran consumo de recursos no sistema.
A segunda opción é preferible cando as transicións de estados son rápidas, xa que evitarase destruír os estados que poderían reutilizarse en breve. Os custos de creación pagaranse ao principio e non existirán custos de destrucción. Nembergantes, esta opción pode non resultar axeitada xa que o Contexto ten que gardar as referencias de tódolos estados aos que pode transitar.
Herdanza dinámica [editar]
Lograríase cambiar o comportamento dunha determinada petición cambiando a clase do obxecto en tempo de execución, pero isto non é posible na maioria dos lenguaxes de programación orientados a obxectos.
Exemplo [editar]
Diagrama de clases [editar]
Diagrama de estados [editar]
Implementación (java) [editar]
/** * ConexionTCP define o contexto para este exemplo do patrón estado. Para * facilitar a comprensión do exemplo supoñemos que unha ConexionTCP pode * estar en dous estados: TCPEstablecida e TCPPechada, de xeito que se optaramos * por representar o estado cun atributo, os métodos da clase ConexionTCP * acabarían por convertirse en condicionais sobre dito estado */ class ConexionTCP { // O constructor da clase, establece el estado inicial (TCPEstablecida). // Coma neste caso os estados da ConexionTCP non teñen estado propio, // utilizamos un singleton public ConexionTCP(int id) { _id = id; _estado = TCPEstablecida.instancia(); } public String toString() { return (_id + " (" + _estado + ")"); } // Este método cambia de estado a ConexionTCP. Problema: o método ten que // ser accedido dende uhna clase externa (EstadoConexionTCP), isto descarta // visibilidade private e protected. public é demasiado xeral dado que // *todas* as clases poderían acceder ao metodo... Neste caso, propondríamos // visibilidade de paquete, con ConexionTCP e os seus estados no mesmo // package... void establecerEstado(EstadoTCP estado) { System.out.println("Transitando do " + _estado + " ao " + estado); _estado = estado; } // Os métodos dependentes do estado delegan o comportamento // definido para cada estado. Dado que imos a responsabilizar aos // estados de efectuar as transicións, pasamos a ConexionTCP ao estado // para que poida, se lle interesa, invocar establecerEstado public void abrir() { _estado.abrir(this); } public void pechar() { _estado.pechar(this); } public void acusarRecibo() { _estado.acusarRecibo(this); } // -------- privadas --------- private EstadoTCP _estado; // implementa a asociación co estado private int _id; }
/** * Esta é a clase abstracta que define as operaciones específicas do estado. Os * métodos declarados poden ser abstractos, polo que as subclases teñen que * implementalos forzosamente, ou poden ter unha implementaciᅵn por defecto, * definida neste nivel. */ abstract class EstadoTCP { // Os métodos abrir, pechar e acusarRecibo son abstractos // (os estados concretos teñen que implementados) // reciben como argumento a conexionTCP para poder, se // procede acceder aos atributos e metodos da conexion. abstract public void abrir(ConexionTCP conexionTCP); abstract public void pechar(ConexionTCP conexionTCP); abstract public void acusarRecibo(ConexionTCP conexionTCP); // Este metodo identifica o estado da conexionTCP // Establécese un valor por defecto que sería usado se as // subclases non o redefinen. public String toString() { return "Descoñecido"; } }
/** * Un dos estados concretos da conexionTCP. A clase TCPEstablecida fai a * transición TCPEstablecida -> TCPPechada al llamar a devolver. Rechaza las * solicitudes (no se contemplan reservas) */ class TCPEstablecida extends EstadoTCP { // Dado que neste exemplo os estados da ConexionTCP non van a conter // atributos dependentes do contexto, TCPEstablecida é un Singleton protected TCPEstablecida() { } public static EstadoTCP instancia() { if (_instancia == null) _instancia = new TCPEstablecida(); return _instancia; } // métodos especificos deste estado concreto. // pechar fai a transición a TCPPechada public void abrir(ConexionTCP conexionTCP) { System.out.println("A conexionTCP " + conexionTCP + " xa está establecida!"); } public void pechar(ConexionTCP conexionTCP) { System.out.println("Ok. Pechando a conexionTCP " + conexionTCP); conexionTCP.establecerEstado(TCPPechada.instancia()); } public void acusarRecibo(ConexionTCP conexionTCP) { System.out.println("Ok. Enviando ACK da conexionTCP " + conexionTCP); } // Redefine o nome do estado public String toString() { return "TCPEstablecida"; } // --------- privadas ------------ // Instancia do Singleton TCPEstablecida private static EstadoTCP _instancia; }
/** * Un dos estados concretos da conexionTCP. A clase TCPPechada fai a transición * TCPPechada -> TCPEstablecida ao chamar a pechar. Rechaza os acuses de recibo. */ class TCPPechada extends EstadoTCP { // Dado que neste exemplo os estados da ConexionTCP non van a conter // atributos dependentes do contexto, TCPPechada é un Singleton protected TCPPechada() { } public static EstadoTCP instancia() { if (_instancia == null) _instancia = new TCPPechada(); return _instancia; } // métodos específicos deste estado concreto. // abrir fai a transición a TCPEstablecida public void abrir(ConexionTCP conexionTCP) { System.out.println("Ok. Abrindo a conexionTCP " + conexionTCP); conexionTCP.establecerEstado(TCPEstablecida.instancia()); } public void pechar(ConexionTCP conexionTCP) { System.out.println("A conexionTCP " + conexionTCP + " xa está pechada!"); } public void acusarRecibo(ConexionTCP conexionTCP) { System.out.println("A conexionTCP " + conexionTCP + ", está pechada, non pode enviar ACKs!"); } // Redefine o nome do estado public String toString() { return "TCPPechada"; } // --------- privadas ------------ // Instancia do Singleton TCPPechada private static EstadoTCP _instancia; }
Patróns relacionados [editar]
Coma xa vimos ó longo deste artículo, o patrón State está relacionado cos patróns:
- Flyweight
- Utilízase cando se quere compartir obxectos estado (EstadoConcreto)
- máis información sobre este patrón
- Singleton
- Utilízase frecuentemente xa que os estados (EstadoConcreto) adoitan ter unha única instancia
- maís información sobre este patrón
Véxase tamén [editar]
Bibliografía [editar]
- E. Gamma, R. Helm, R. Johnson, J. Vlissides. Design Patterns. Elements of Reusable Object-Oriented Software. Addison-Wesley Professional Computing Series.
- M. Grand. Patterns in Java, a catalog of reusable design patterns illustrated with UML. Volume I. John Wiley & Sons.


