State (padrón de deseño)

Na Galipedia, a Wikipedia en galego.

O padró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 padrón State (Estado) é un padró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 padró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 adopte 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 actuase dun xeito diferente en base ao estado no que se encontrase.

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 padró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]

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 adóitase utilizar o padró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 padró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 sentenzas condicionais con múltiples ramas que dependen do estado do obxecto. Este estado adóitase representar por unha ou máis constantes numeradas. Coa aplicación do padrón, 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 doutros obxectos.

Consecuencias da aplicación do padrón[editar | editar a fonte]

A aplicación deste padró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 se precisaría 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 padrón non especifica que participante define os criterios para as transicións entre estados. Se os criterios son fixos, poden implementarse no Contexto. Non obstante, adoita ser máis 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, isto 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 se evitará destruír os estados que poderían reutilizarse en breve. Os custos de creación pagaranse ao principio e non existirán custos de destrución. Porén, 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 maioría das linguaxes de programación orientadas a obxectos.

Exemplo[editar | editar a fonte]

Diagrama de clases[editar | editar a fonte]

Diagrama de estados[editar | editar a fonte]

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

/**
 * ConexionTCP define o contexto para este exemplo do padró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 converterse 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 unha 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;
}

Padróns relacionados[editar | editar a fonte]

Coma xa vimos ao longo deste artigo, o padrón State está relacionado cos padró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.