padrão state_ cinto de utilidades MÁQUINAS DE ESTADO COM O Padrão de Projeto State Desenvolver software é trabalhar com abstrações o tempo todo, pois estas nos ajudam a gerenciar a complexidade nos sistemas que desenvolvemos. Uma das abstrações mais poderosas que podemos empregar são as máquinas de estado. Com elas, é possível até complexos autômatos para uso em compiladores máquina de estados é extremamente interessante por ser de fácil construção, sucinto e altamente comunicativo, facilitando bastante a comunicação com os usuários do sistema e especialistas de domínio. No entanto, o que acontece é que muitos utilizam a modelagem de máquinas de estados apenas em nível conceitual, perdendo-o de vista no momento da implementação. Muitos até fazem a modelagem em máquina de estados de uma maneira mais formal usando algo como UML1, mas apenas para propósitos de documentação de requisitos e comu- estados é convertida em diversos ifs, que se espalham por variadas partes do sistema, aumentando as lacunas sintática e semântica entre o modelo de domínio discutido pelas pessoas e o modelo de representar um conceito da linguagem do domínio explicitamente em código, como advogado pelo Domain-driven Design 2. Mas não precisa ser assim! 1. Máquinas de estados são um recurso de modelagem também previsto 2. Para mais detalhes sobre Domain-driven Design, recomendo fortemente Neste artigo, mostraremos como implementar máquinas de estado por meio do padrão de projeto State. Primeiramente, faremos um resumo do padrão e, em seguida, ilustraremos seu uso por meio de um exemplo prático associado com o uso da Java Persistence API. Ressaltamos que este artigo assume que o leitor já possui um conhecimento básico do funcio- desejam mais informações, ver seção de referências). O padrão State O padrão State é um dos 23 padrões de projeto da Gang of Four (GoF), já velho conhecido da comunidade (o livro foi publicado em 1994). Apesar de alguns desses padrões hoje serem até meio controversos (como é o padrão Singleton, por exemplo), o padrão State (juntamente com seu irmão gêmeo Strategy) continua sendo de elevada aplicabilidade nos sistemas desenvolvidos atualmente. A ideia do State é representar os estados de uma máquina de estados por meio de classes que implementam uma interface comum, a qual contém as regras de transição. Cada classe implementa, então, a ação devida quando a transição é acionada por essa interface. O outro elemento do padrão é o chamado contexto, que é a classe da qual o estado faz parte e também é a classe com a qual o restante do sistema interage. Quando uma regra de negócio é acionada a leitura do livro homônimo de Eric Evans. / 26
Alexandre Gazola alexandregazola@gmail.com @alexandregazola Bacharel em Ciência da Computação pela Universidade Federal de Viçosa (UFV) e mestre em Informática pela PUC-Rio. Traba- e editor-técnico da revista MundoJ, além de manter um blog em http://alexandregazola.wordpress.com. Bacharel em Ciências da open source, como KDE e Mentawai, e é da equipe de arquitetura da CodeIT Solutions, uma empresa especializada na prestação de serviços de desenvolvimento de software para as indústrias de seguros. Máquinas de estados são uma construção extremamente comum para se modelar requisitos de diversos sistemas. Normalmente, são fáceis de desenhar (em forma de modelos) e interessantes instrumentos para comunicação e validação com os usuários por serem de simples entendimento. No entanto, muitos desenvolvedores perdem artigo, abordamos o padrão de projeto State, bastante útil para modelagem de máquinas de estado em código. Como sempre, damos uma ênfase mais prática e direta, mostrando uma opção de implementação do padrão em Java usando Enums e a Java Persistence API. ção de uma transição de estado, o contexto dispara do de realizar a ação correspondente e determinar passado como parâmetro ao estado para que o estado possa decidir o estado seguinte, setando um obje- estrutura geral do padrão State. Vamos à seção seguinte, na qual utilizaremos um exemplo concreto para o qual aplicaremos o padrão. Aplicando o padrão State na prática Como ilustração, imaginemos uma aplicação e publicação de artigos para um periódico qualquer. Neste exemplo, estamos interessados em modelar os estados e transições de um artigo. Após discussão CONTEXT + REQUEST ( ) STATE.HANDLE ( ) CONCRETESTATE A Figura 1. Estrutura geral do padrão State. STATE com os usuários, chegamos ao diagrama de estados O modelo apresentado é bastante autoexplicativo. Os estados são representados pelos retângulos e as transições por setas. Tudo começa quando um artigo é submetido pelo sistema. Quando isso acontece, ele vai para o estado Recebido. A partir desse estado, o sistema realiza algumas validações automáticas no artigo, como, por exemplo, ortogra- automática determina se o artigo irá para o estado Aceito para revisão ou Recusado. No estado Recusado, o usuário pode fazer alterações à vontade e realizar uma nova submissão. Uma vez que um artigo esteja Aceito para revisão, um humano fará a leitura e revisão do artigo, determinando se o mesmo está satisfatório, se apresenta erros básicos ou se, simplesmente, foi recusado. Cada um desses eventos faz com que o artigo vá, respectivamente, para os estados Aceito para publicação, Aceito com correções e Recusado. CONCRETESTATE B Implementação tradicional do padrão Agora, vamos ao que interessa: o código! A Listagem 1 apresenta a interface para os estados do nosso modelo, contendo as possíveis transições da máquina. Na Listagem 2 trazemos 27 \
INÍCIO ACEITO PARA REVISÃO satisfatório ACEITO PARA PUBLICAÇÃO validado com sucesso apresenta erros básicos satisfatório ACEITO COM CORREÇÕES submetido RECEBIDO validado sem sucesso submetido recusado recusado RECUSADO FIM Figura 2. Modelagem da máquina de estados para o sistema de publicação de artigos. um exemplo simples de implementação da interface para o estado Recebido. Repare que o código é bastante simples, apenas cabendo ao estado em questão decidir qual é o estado seguinte. As transições que não fazem sentido para um dado estado lançam a exceção não checada UnsupportedOperationException. Listagem 1. Interface para os estados possíveis de um artigo. public interface EstadoArtigo { void submetido(artigo artigo); void validadocomsucesso(artigo artigo); void validadosemsucesso(artigo artigo); void satisfatorio(artigo artigo); void apresentaerrosbasicos(artigo artigo); void recusado(artigo artigo); Listagem 2. Implementação do estado recebido. public class Recebido implements EstadoArtigo { public void submetido(artigo artigo) { throw new UnsupportedOperationException( O artigo está em um estado inválido para a ocorrência do evento submetido : + this); public void validadocomsucesso(artigo artigo) { artigo.setestado(new AceitoParaRevisao()); public void validadosemsucesso(artigo artigo) { artigo.setestado(new Recusado()); /* lançando UnsupportedOperationException igual a submetido()... */ A Listagem 3 exibe o código da classe principal (o contexto do padrão State), por meio da qual o restante da aplicação interage (por isso, os estados podem ter visibilidade restrita apenas ao pacote em que está a classe de contexto). Repare que a classe artigo não precisa ter conhecimento de qual é o estado seguinte ao do evento ocorrido, isso passa a ser uma responsabilidade do estado com que o artigo está atualmente plementação tradicional, provavelmente não haveria os métodos de transição na classe Artigo, e a lógica mente por outras partes do sistema (com os estados modelados talvez até como constantes inteiras, acredite se quiser!). A Listagem 4 mostra um exemplo de como poderia ser esse código sem o padrão. Listagem 3. Classe Artigo (faz o papel de contexto no padrão State). public class Artigo { private EstadoArtigo estado; public void setestado(estadoartigo estado) { this.estado = estado; / 28
public void submetido() { estado.submetido(this); public void validadocomsucesso() { estado.validadocomsucesso(this); public void validadosemsucesso() { estado.validadosemsucesso(this); Listagem 4. Exemplo de implementação tradicional sem usar o padrão State. public class RevisaoArtigoService { /* mais código aqui */ public void revisar(artigo artigo) { /* mais código aqui */ if (artigo.getestado().equals( ACEITO_PARA_REVISAO)) { if (estasatisfatorio(artigo)) { artigo.setestado(aceito_para_publicacao); else if (correcaopodeserfeita(artigo)) { artigo.setestado(aceito_com_correcoes); else { artigo.setestado(recusado); Implementação do padrão com Enum e JPA Uma forma interessante de se implementar os estados é por meio do uso dos tipos enumerados (Enums) oferecidos pelo Java. A ideia é simples: cria-se uma enum com métodos para cada transição e constantes que implementem esses métodos de acordo com suas respectivas lógicas. Normalmente, numa aplicação real, precisaremos armazenar os dados dos artigos e seus estados em um banco de dados. Vamos assumir que queremos usar a Java Persistence API (JPA) com Hibernate para armazenar os objetos no banco de dados, incluindo aí os estados da máquina. O problema, neste caso, é que o Hibernate não oferece um suporte adequado à persistência de enums (existe anotação para isso, mas existem problemas para seu uso usar strings para mapeamento de chaves etc.). Mesmo numa implementação de máquina de estados sem enums, questão dos objetos transientes que já existem dentro do banco (quando um estado necessita realizar uma transição, ele criaria uma nova instância de um estado já existente). Para contornar esse problema, podemos representar as constantes em duas partes, usando uma classe principal (esta, mapeada via Hibernate), a qual delega para um enum que representa o estado da máquina de estados propriamente dito. A Listagem 5 mostra um exemplo de como poderia ser esse código. Desta forma, é possível utilizar as constantes da classe EstadoArtigo (a lógica da máquina de estados também das facilidades de persistência oferecidas pelo Hibernate. Repare que implementamos no contexto da enum todos os métodos de transição, mas com uma implementação default (como se fosse uma classe abstrata). Dessa forma, as constantes enume- zerem sentido. Lembramos que a classe de contexto, Artigo, permanece com o mesmo código. Listagem 5. Implementação do padrão State com Enums e suporte de JPA/Hibernate. @Entity public class EstadoArtigo { public static EstadoArtigo RECEBIDO = new EstadoArtigo(1, EstadosMaquinaEstados. RECEBIDO); public static EstadoArtigo RECUSADO = new EstadoArtigo(2, EstadosMaquinaEstados. RECUSADO); public static EstadoArtigo ACEITO_PARA_REVISAO= new EstadoArtigo(3, EstadosMaquinaEstados. ACEITO_PARA_REVISAO); public static EstadoArtigo ACEITO_PARA_PUBLICACAO=new EstadoArtigo(4, EstadosMaquinaEstados.ACEITO_PARA_PUBLICACAO); public static EstadoArtigo ACEITO_COM_CORRECOES = new EstadoArtigo(5, EstadosMaquinaEstados.ACEITO_COM_CORRECOES); @Id private int private EstadosMaquinaEstados estadomaquina; private EstadoArtigo(int EstadosMaquinaEstados estadomaquina) { this this.estadomaquina = estadomaquina; protected void submetido(artigo artigo) { estadomaquina.submetido(artigo); protected void validadocomsucesso(artigo artigo) { estadomaquina.validadocomsucesso(artigo); protected void validadosemsucesso(artigo artigo) { estadomaquina.validadosemsucesso(artigo); 29 \
protected void satisfatorio(artigo artigo) { estadomaquina.satisfatorio(artigo); protected void apresentaerrosbasicos(artigo artigo) { estadomaquina.apresentaerrosbasicos(artigo); protected void recusado(artigo artigo) { estadomaquina.recusado(artigo); private static enum EstadosMaquinaEstados { void satisfatorio(artigo artigo) { lancarexcecaodefault( satisfatorio ); void apresentaerrosbasicos(artigo artigo) { lancarexcecaodefault( apresentaerrosbasicos ); private void lancarexcecaodefault(string evento) { throw new UnsupportedOperationException( O artigo está em um estado inválido para a ocorrência do evento + evento + : + this); RECEBIDO { void validadocomsucesso(artigo artigo) { ACEITO_PARA_REVISAO); void validadosemsucesso(artigo artigo) { RECUSADO);, ACEITO_PARA_REVISAO { void satisfatorio(artigo artigo) { ACEITO_PARA_PUBLICACAO); void apresentaerrosbasicos(artigo artigo) { ACEITO_COM_CORRECOES); void recusado(artigo artigo) { RECUSADO);, /* implementacao das transicoes omitidas para os seguintes estados... */ RECUSADO, ACEITO_PARA_PUBLICACAO, ACEITO_COM_CORRECOES; Máquinas de estados são uma abstração recorrente em diversos tipos de sistema, constituindo um poderoso instrumento de modelagem e comunicação para usuários e desenvolvedores. Neste artigo, vimos como podemos levar as vantagens desse modelo até o nível do código por meio do padrão State, evitando- -se, assim, o espalhamento das lógicas de transição por diferentes partes do sistema. Destacamos também uma possibilidade de implementação particular para o caso em que é necessária a persistência dos es- Procura conhecer o estado das tuas ovelhas; cuida bem dos teus rebanhos. (Pv 27:23) Gazola /para saber mais /referências void submetido(artigo artigo) { lancarexcecaodefault( submetido ); void validadocomsucesso(artigo artigo) { lancarexcecaodefault( validadocomsucesso ); void validadosemsucesso(artigo artigo) { lancarexcecaodefault( validadosemsucesso ); void recusado(artigo artigo) { lancarexcecaodefault( recusado ); / 30