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.

Introdución[editar | editar a fonte]

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 | editar a fonte]

Permite que un obxecto modifique o seu comportamento cando cambia o seu estado interno.

Motivación[editar | editar a fonte]

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 | editar a fonte]

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 | editar a fonte]

PatronEstado DiagramaClasesEstructura.png

Participantes[editar | editar a fonte]

  • 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 | editar a fonte]

  • 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 | editar a fonte]

É 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 | editar a fonte]

A aplicación deste patrón ten as seguintes consecuencias:

Vantaxes[editar | editar a fonte]

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 | editar a fonte]

Pola contra, teremos a seguinte desvantaxe:

  • O deseño será menos compacto: Xa que aumenta o número de clases.

Implementación[editar | editar a fonte]

Transicións entre estados[editar | editar a fonte]

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 conveniente 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 | editar a fonte]

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 | editar a fonte]

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 | editar a fonte]

Diagrama de clases[editar | editar a fonte]

Diagrama clases do exemplo do patron State.png

Diagrama de estados[editar | editar a fonte]

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

Implementación (java)[editar | editar a fonte]

/**
 * 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 | editar a fonte]

Coma xa vimos ó longo deste artículo, o patrón State está relacionado cos patróns:

Véxase tamén[editar | editar a fonte]

Bibliografía[editar | editar a fonte]

  • 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.