sábado, 20 de novembro de 2010

Padrão de Projeto "State"

O objetivo deste post é apresentar o padrão de projeto state, iniciando a série de padrões de projeto que serão publicados aqui. Afim de ser didático, inciarei com um exemplo e ao final apresentarei a estrutura do padrão.

Suponha que objetos de uma classe transitem por estados em uma máquina de estados. Por exemplo, imagine que uma conexão com um servidor possa transitar pelos estados desconectada, conectada e aguardando resposta, supondo que, uma vez que uma requisição é enviada, a conexão permanece no estado aguardando resposta até que a resposta seja recebida do servidor. Os serviços fornecidos pela classe que representa essa conexão são: conectar, desconectar, enviar requisição e receber resposta. Tal classe se assemelharia àquela representada abaixo.


É fácil perceber que o comportamento da classe deve mudar dependendo do estado em que sua instância se encontra. Por exemplo, caso o objeto esteja no estado conectado ou aguardando resposta e alguém chame a operação "conectar", um erro deve ser gerado. Mas, caso o mesmo método seja chamado quando o objeto estiver no estado desconectado, a conexão deverá ser estabelecida e o estado do objeto deve ser alterado para conectado. O diagrama abaixo apresenta a máquina de estados da conexão, as operações que causam mudança de estado e as operação que devem ser executadas sem erro em cada estado.


Uma solução possível para a mudança de comportamento entre os estados é, em cada método da classe "Conexao", escolher a implementação correta utilizando cláusulas if/else ou switch/case. Porém, ao surgir um novo estado, o desenvolvedor deve tomar o cuidado de incluir a opção correta em cada método, aumentando a possibilidade de inserção de erros na implementação.

O catálogo de padrões de projeto GoF (Gang of Four), cujo o livro irei referenciar no final deste post, propõe o padrão de projeto State para solucionar problemas como o descrito acima. Antes de apresentar a estrutura do padrão, trabalhei no exemplo da classe Conexao para explicar o funcionamento do padrão.

A solução proposta pelo GoF consiste em associar a classe "Conexao" a uma interface que represente o estado atual da conexão e, para cada estado, realizar a interface em classes específicas que realmente terão as implementações dos estados. A interface terá, basicamente, todas as operações da classe "Conexao", porém recebendo um parâmetro a mais que é a referência para aquela classe. Caso a implementação de uma operação pressuponha mudança de estado, a classe que representa o estado anterior terá a incumbência de alterar o estado da conexão para o próximo estado válido. O diagrama abaixo apresenta tal estrutura de classes.


Com isso, a implementação da classe "Conexao" se torna bastante simplificada, sem a necessidade de ter que saber o que fazer em qual estado. Basta inicializar o atributo state com o estado inicial correto e, para cada chamada de operação, repassar a chamada para o estado. Para o código que utilizará a estrutura, nada muda, pois a única classe que precisa ser conhecida ainda é a classe "Conexao" e as operações chamadas ainda serão as mesmas. Segue abaixo o código Java da classe "Conexao".

public class Conexao {

 private ConexaoState state;

 public Conexao() {
  state = new Desconectado();
 }

 public void conectar() {
  state.conectar(this);
 }

 public void desconectar() {
  state.desconectar(this);
 }

 public void enviarRequisicao(Requisicao requisicao) {
  state.enviarRequisicao(requisicao, this);
 }

 public Resposta receberResposta() {
  return state.receberResposta(this);
 }

 public void setState(ConexaoState state) {
  this.state = state;
 }

}

Para exemplificar, segue um esboço do código Java da classe que representa o estado conectado, alterando, quando necessário, o estado da conexão ao final do método.

public class Conectado implements ConexaoState {

 public void conectar(Conexao conexao) {
  throw new ConexaoException("Estado inválido");
 }

 public void desconectar(Conexao conexao) {
  // TODO: Desconectar do servidor.
  conexao.setState(new Desconectado());
 }

 public void enviarRequisicao(Requisicao requisicao, Conexao conexao) {
  // TODO: Enviar requisicao para o servidor.
  conexao.setState(new Conexao());
 }

 public Resposta receberResposta(Conexao conexao) {
  throw new ConexaoException("Estado inválido");
 }

}

A implementação acima está de acordo com o GoF, mas alterações são permitidas para otimizar o funcionamento para um problema específico. Por exemplo, se algumas operações terão o mesmo funcionamento em diversos estados, podemos substituir a interface que define o estado por uma classe abstrata com a implementação padrão de tais operações. Se quisermos evitar que os estados sejam instanciados no momento em que a classe for alterada para cada um deles, podemos usar uma estrutura de dados, como uma hashtable, onde consultaremos as instâncias já existentes dos estados, evitando consumo desnecessário de memória e execução desnecessária do processo de garbage collection.

Abaixo segue a estrutura do padrão de projeto:


Onde:

Contexto - classe contendo a interface pública de interesse dos clientes.
Estado - interface que define o comportamento de um estado em particular.
EstadoConcreto - classe que implementa um estado em particular.

Mais informações podem ser encontradas no livro "Design Patterns: Elements of Reusable Object-Oriented Software", de Erich Gamma, Richard Helm, Ralph Johnson e John Vlissides. Devido ao número de autores, o livro é também conhecido como GoF (Gang of Four - Gangue dos Quatro).

5 comentários:

  1. Muito bom , aprendi o STATE super rápido graças a Seu post.

    Alguns outros posts falam de uma forma muito técnica e dessa forma está bem mais didática. Vendo na prática como funciona.

    ResponderExcluir
  2. Reitero o post acima, só fui conseguir aplicar state ao meu projeto graças ao seu post
    Inclusive voltei aqui pra procurar se você não explicava o observer. Uma pena que você não continuou o blog :(

    ResponderExcluir
  3. Muito bom seu post. Mas não entendi uma coisa, onde tá a classe Requisicao no diagrama de classe? Já que você está fazendo uso dela no código. Obrigado!

    ResponderExcluir
    Respostas
    1. Assim como a classe Resposta. Não estou conseguindo entender. Seu diagrama diz uma coisa e seu código outra. Poderia me ajudar melhor? Obrigado!

      Excluir
    2. Olá, obrigado pelo comentário.
      Não incluí as classes Requisicao e Resposta no diagrama pois estão fora do contexto do padrão de projetos State, mas entenda como as classes que representam, respectivamente, a requisição enviada através da conexão com o servidor e a resposta a essa requisição. As classes também estão sendo usadas na assinatura das operações apresentadas nos diagramas, logo, o código está condizente com o diagrama.
      Espero ter esclarecido.

      Excluir