Design Patterns – State – Parte 4

Você precisa estar logado para dar um feedback. Clique aqui para efetuar o login
Para efetuar o download você precisa estar logado. Clique aqui para efetuar o login
Confirmar voto
0
 (6)  (0)

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.

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 R$ {0}, saldo atual R$ {1}.", valor, this.Saldo);
                  if (this.Saldo > 0)
                      this.MeuEstado = ContaState.saldoNegativo;
                  break;
              case ContaState.saldoNegativo:
                  this.Saldo -= valor;
                  Console.WriteLine("Retirado R$ {0}, saldo atual R$ {1}.", valor, this.Saldo);
   
                  if (this.Saldo < -100.00)
                  {
                      this.MeuEstado = ContaState.bloqueado;
                  }
                  break;
              case ContaState.bloqueado:
                  Console.WriteLine("Conta bloqueada, saque cancelado, saldo atual R$ {1}.", valor, this.Saldo);
                  break;
              default:
                  break;
          }
          Console.WriteLine("Estado da conta: {0}\n", this.MeuEstado.ToString());
      }
   
      public void Deposito(Double valor)
      {
          this.Saldo += valor;
          if (this.Saldo <= -100.00)
              this.MeuEstado = ContaState.bloqueado;
          else if (this.Saldo >= 0)
              this.MeuEstado = ContaState.saldoPositivo;
          else
              this.MeuEstado = ContaState.saldoNegativo;
   
          Console.WriteLine("Foi depositado R$ {0}, saldo atual R$ {1}", valor, this.Saldo);
          Console.WriteLine("Estado da conta: {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 R$ {0}, saldo atual R$ {1}.", valor, this._conta.Saldo);
          if (this._conta.Saldo < 0)
              if (this._conta.Saldo < -100.00)
                  this._conta.MeuEstado = new bloqueado(this._conta);
              else
                  this._conta.MeuEstado = new saldoNegativo(this._conta);
      }
   
      public void Deposito(double valor)
      {
          this._conta.Saldo += valor;
          Console.WriteLine("Foi depositado R$ {0}, saldo atual R$ {1}", valor, this._conta.Saldo);
          if (this._conta.Saldo < 0)
              if (this._conta.Saldo < -100.00)
                  this._conta.MeuEstado = new bloqueado(this._conta);
              else
                  this._conta.MeuEstado = new saldoNegativo(this._conta);
      }
      #endregion
  }
   
  public class saldoNegativo : IContaState
  {
      private Conta _conta;
   
      public saldoNegativo(Conta PConta)
      {
          this._conta = PConta;
      }
   
      #region [IContaState Members]
      public void Saque(double valor)
      {
          this._conta.Saldo -= valor;
          Console.WriteLine("Retirado R$ {0}, saldo atual R$ {1}.", valor, this._conta.Saldo);
   
          if (this._conta.Saldo < -100.00)
              this._conta.MeuEstado = new bloqueado(this._conta);
      }
   
      public void Deposito(double valor)
      {
          this._conta.Saldo += valor;
          Console.WriteLine("Foi depositado R$ {0}, saldo atual R$ {1}", valor, this._conta.Saldo);
          if (this._conta.Saldo >= -100.00)
              if (this._conta.Saldo < 0)
                  this._conta.MeuEstado = new saldoNegativo(this._conta);
              else
                  this._conta.MeuEstado = new saldoPositivo(this._conta);
      }
      #endregion
  }
   
  public class bloqueado : IContaState
  {
      private Conta _conta;
   
      public bloqueado(Conta PConta)
      {
          this._conta = PConta;
      }
   
      #region [IContaState Members]
      public void Saque(double valor)
      {
          Console.WriteLine("Conta bloqueada, saque cancelado, saldo atual R$ {1}.", valor, this._conta.Saldo);
      }
   
      public void Deposito(double valor)
      {
          this._conta.Saldo += valor;
          Console.WriteLine("Foi depositado R$ {0}, saldo atual R$ {1}", valor, this._conta.Saldo);
          if (this._conta.Saldo < 0)
          {
              if (this._conta.Saldo < -100.00)
                  this._conta.MeuEstado = new bloqueado(this._conta);
          }
          else
          {
              this._conta.MeuEstado = new saldoPositivo(this._conta);
          }
      }
      #endregion
  }

Os pontos importantes que devemos observar são:

· A variável privada da classe Conta (vamos ver ela daqui pouco);

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

Bom chegamos ao final de mais um artigo, espero novamente que eu tenha passado a ideia central do assunto.

Não deixe de ver os artigos anteriores e aguardem os próximos.

Fontes:

FREEMAN, ERIC & FREEMAN, ELISABETH – Use a Cabeça! Padrões de Projetos (Design Patterns), 2ª Edição.

 
Você precisa estar logado para dar um feedback. Clique aqui para efetuar o login
Receba nossas novidades
Ficou com alguma dúvida?