Design Patterns – State – Parte 4

Vamos continuar com a série Design Patterns apresentando o padrão State, não é um padrão com um grau de dificuldade alta mais não é tão simples como o singleton exibido no último artigo.

Vamos continuar com a série Design Patterns apresentando o padrão State, não é um padrão com um grau de dificuldade alta mais não é tão simples como o singleton exibido no ultimo artigo. O padrão state permite que um objeto altere o seu comportamento quando o seu estado interno muda. O objeto parecerá ter mudado de classe.

O padrão encapsula os estados em classes separadas e delega as tarefas para o objeto que representa o estado atual, nós sabemos que os comportamentos mudam juntamento com o estado interno.

A seguir temos o diagrama de classe:

Obs: Diagrama resumido para termos só o necessário para representação e ficar fácil o entendimento. Vamos analisar o diagrama.

O contexto é a classe que pode ter vários estados internos diferentes.

A interface estado define uma interface comum para todos os estados concretos. Como são intervambiaveis, todos devem implementar a mesma interface.

Os estado concretos (podemos ter vários estados concretos) lidam com as solicitações provenientes do contexto. Cada estado concreto fornece a sua própria implementação de uma solicitação. Assim, quando o contexto muda de estado, seu comportamento também muda.

Sempre que uma solicitação() é feita ao contexto, ela é delegada ao estado apropriado para ser processado.

Agora vamos imaginar um cenário, vamos imaginar uma conta corrente bem simples com opção de depositar e sacar dinheiro e já imaginamos os estado que essa conta pode estar saldopositivo, saldonegativo e bloqueado.

Vou exibir uma implementação sem utilizar o padrão state para mostrar o quanto ficamos amarrados em ifs e cases deixando a nossa manutenção um pouco complicado pois você pode alterar algo e atrapalhar o funcionamento de tudo o que já estava funcionando e até mesmo validado.

nbsp;,saldoatualRnbsp;.",valor,this.Saldo); if(this.Saldo>0) this.MeuEstado=ContaState.saldoNegativo; break; caseContaState.saldoNegativo: this.Saldo-=valor; Console.WriteLine("RetiradoRnbsp;,saldoatualRnbsp;.",valor,this.Saldo); if(this.Saldo<-100.00) { this.MeuEstado=ContaState.bloqueado; } break; caseContaState.bloqueado: Console.WriteLine("Contabloqueada,saquecancelado,saldoatualRnbsp;.",valor,this.Saldo); break; default: break; } Console.WriteLine("Estadodaconta:\n",this.MeuEstado.ToString()); } publicvoidDeposito(Doublevalor) { this.Saldo+=valor; if(this.Saldo<=-100.00) this.MeuEstado=ContaState.bloqueado; elseif(this.Saldo>=0) this.MeuEstado=ContaState.saldoPositivo; else this.MeuEstado=ContaState.saldoNegativo; Console.WriteLine("FoidepositadoRnbsp;,saldoatualR
public enum ContaState { saldoPositivo, saldoNegativo, bloqueado } public class Conta { public Conta() { this.Saldo = 0; this.MeuEstado = ContaState.saldoPositivo; } public Conta(Double valor) { this.Deposito(valor); } public Double Saldo { get; set; } public ContaState MeuEstado { get; set; } public void Saque(Double valor) { switch (MeuEstado) { case ContaState.saldoPositivo: this.Saldo -= valor; Console.WriteLine("Retirado Rnbsp;",valor,this.Saldo); Console.WriteLine("Estadodaconta:\n",this.MeuEstado.ToString()); } }

Agora vamos aplicar toda teoria que vimos sobre state, vamos encapsular cada estado em uma classe, e para a alteração o estado da classe contexto vamos ter uma ação invocada (Saque ou Deposito), veja o fluxo geral do estou dizendo:

E como implementaremos isso? Primeiro vamos ter que criar uma interface para os estados:

public interface IContaState { void Saque(Double valor); void Deposito(Double valor); }

Teremos só dois métodos: saque e deposito. Vamos implementar três estados fazendo um contrato com a interface IContaState. Você se lembra do enum (saldoPositivo, saldoNegativo, bloqueado) criado no exemplo sem o padrão? Pois bem, vamos ter uma classe para cada uma daquelas opções.

public class saldoPositivo : IContaState { private Conta _conta; public saldoPositivo(Conta PConta) { this._conta = PConta; } #region [IContaState Members] public void Saque(double valor) { this._conta.Saldo -= valor; Console.WriteLine("Retirado Rnbsp;, saldoatualRnbsp;.",valor,this._conta.Saldo); if(this._conta.Saldo<0) if(this._conta.Saldo<-100.00) this._conta.MeuEstado=newbloqueado(this._conta); else this._conta.MeuEstado=newsaldoNegativo(this._conta); } publicvoidDeposito(doublevalor) { this._conta.Saldo+=valor; Console.WriteLine("FoidepositadoRnbsp;, saldoatualRnbsp;",valor,this._conta.Saldo); if(this._conta.Saldo<0) if(this._conta.Saldo<-100.00) this._conta.MeuEstado=newbloqueado(this._conta); else this._conta.MeuEstado=newsaldoNegativo(this._conta); } #endregion } publicclasssaldoNegativo:IContaState { privateConta_conta; publicsaldoNegativo(ContaPConta) { this._conta=PConta; } #region[IContaStateMembers] publicvoidSaque(doublevalor) { this._conta.Saldo-=valor; Console.WriteLine("RetiradoRnbsp;, saldoatualRnbsp;.",valor,this._conta.Saldo); if(this._conta.Saldo<-100.00) this._conta.MeuEstado=newbloqueado(this._conta); } publicvoidDeposito(doublevalor) { this._conta.Saldo+=valor; Console.WriteLine("FoidepositadoRnbsp;, saldoatualRnbsp;",valor,this._conta.Saldo); if(this._conta.Saldo>=-100.00) if(this._conta.Saldo<0) this._conta.MeuEstado=newsaldoNegativo(this._conta); else this._conta.MeuEstado=newsaldoPositivo(this._conta); } #endregion } publicclassbloqueado:IContaState { privateConta_conta; publicbloqueado(ContaPConta) { this._conta=PConta; } #region[IContaStateMembers] publicvoidSaque(doublevalor) { Console.WriteLine("Contabloqueada,saquecancelado, saldoatualRnbsp;.",valor,this._conta.Saldo); } publicvoidDeposito(doublevalor) { this._conta.Saldo+=valor; Console.WriteLine("FoidepositadoRnbsp;,saldoatualRnbsp;",valor,this._conta.Saldo); if(this._conta.Saldo<0) { if(this._conta.Saldo<-100.00) this._conta.MeuEstado=newbloqueado(this._conta); } else { this._conta.MeuEstado=newsaldoPositivo(this._conta); } } #endregion }

Os pontos importantes que devemos observar são:

Os construtores recebem a própria classe contexto para a criação do estado.

Como todas as classes que vão representar um possível estado para nossa classe contexto tem um contrato com a interface IContaState podemos criar uma variável na nossa classe a partir dessa interface para representar o estado.

Vamos ver como fica nossa classe Conta (contexto):

public class Conta { public Conta() { this.Saldo = 0; } public Conta(Double valor) { this.MeuEstado = new saldoPositivo(this); this.Deposito(valor); } public Double Saldo { get; set; } public IContaState MeuEstado; public void Saque(Double valor) { this.MeuEstado.Saque(valor); Console.WriteLine("Estado da conta: \n", this.MeuEstado.ToString()); } public void Deposito(Double valor) { this.MeuEstado.Deposito(valor); Console.WriteLine("Estado da conta: \n", this.MeuEstado.ToString()); } }

Em comparação a classe criada sem o padrão, temos uma classe menor e mais simples, uma vez que a decisão de qual estado ele vai estar depois da chamada dos métodos não cabe mais a classe contexto e sim a cada classe encapsulada que representa um estado do mesmo modo que a execução do método que esta sendo delegado ao estado atual. Devemos observar também que para setarmos qualquer estado estamos utilizamos “MeuEstado = new [EstadoConcreto]([Contexto])” isso ocorre tanto na criação da classe contexto quanto nas alterações dos estados.

Em ambas implementações com ou sem design pattern o resultado é o mesmo, então vocês estão se perguntando, por que eu irei ter um trabalho maior para chegar ao mesmo resultado?

A resposta é simples, no primeiro se você precisar de mais um estado vai ter que alterar sua classe Conta podendo alterar um bloco de código de forma errada causando transtornos, uma vez que a classe conta é a classe principal, agora se estiver no padrão não necessitaremos de alterar nada na classe conta, e sim na classe do estado especifico da alteração deixando da forma que estava as demais classes dos estado, e se necessário criar outra classe para estado as alterações serão pequenas.

Artigos relacionados