State (patrón de deseño)

Na Galipedia, a Wikipedia en galego.

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]

PatronEstado DiagramaClasesEstructura.png

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:

  1. Crealos só cando se precisen e destruílos despois.
  2. 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 clases do exemplo do patron State.png

Diagrama de estados [editar]

Diagrama de estados do exemplo do patrón State.png

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:

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.