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:
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;{0},saldoatualRnbsp;{1}.",valor,this.Saldo);
if(this.Saldo>0)
this.MeuEstado=ContaState.saldoNegativo;
break;
caseContaState.saldoNegativo:
this.Saldo-=valor;
Console.WriteLine("RetiradoRnbsp;{0},saldoatualRnbsp;{1}.",valor,this.Saldo);
if(this.Saldo<-100.00)
{
this.MeuEstado=ContaState.bloqueado;
}
break;
caseContaState.bloqueado:
Console.WriteLine("Contabloqueada,saquecancelado,saldoatualRnbsp;{1}.",valor,this.Saldo);
break;
default:
break;
}
Console.WriteLine("Estadodaconta:{0}\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;{0},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;{1}",valor,this.Saldo);
Console.WriteLine("Estadodaconta:{0}\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;{0},
saldoatualRnbsp;{1}.",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;{0},
saldoatualRnbsp;{1}",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;{0},
saldoatualRnbsp;{1}.",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;{0},
saldoatualRnbsp;{1}",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;{1}.",valor,this._conta.Saldo);
}
publicvoidDeposito(doublevalor)
{
this._conta.Saldo+=valor;
Console.WriteLine("FoidepositadoRnbsp;{0},saldoatualRnbsp;{1}",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:
- A variável privada da classe Conta (vamos ver ela daquipouco);
- Os construtores que recebem como parâmetro um objeto da classe Conta.
- A variável _conta representar nossa classe contexto dentro do estado e vai ser utilizado para todas as alterações e consulta de valores da Conta.
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: {0}\n", this.MeuEstado.ToString());
}
public void Deposito(Double valor)
{
this.MeuEstado.Deposito(valor);
Console.WriteLine("Estado da conta: {0}\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.