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:

img
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;{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:

img

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.