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).
Muito bom , aprendi o STATE super rápido graças a Seu post.
ResponderExcluirAlguns outros posts falam de uma forma muito técnica e dessa forma está bem mais didática. Vendo na prática como funciona.
Reitero o post acima, só fui conseguir aplicar state ao meu projeto graças ao seu post
ResponderExcluirInclusive voltei aqui pra procurar se você não explicava o observer. Uma pena que você não continuou o blog :(
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!
ResponderExcluirAssim como a classe Resposta. Não estou conseguindo entender. Seu diagrama diz uma coisa e seu código outra. Poderia me ajudar melhor? Obrigado!
ExcluirOlá, obrigado pelo comentário.
ExcluirNã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.