O State é um padrão que utiliza composição de classes para representar a variação de comportamento de uma determinada entidade durante a execução de um sistema. Segundo Guerra, essas classes seguem uma abstração comum (interface ou superclasse) e os métodos de negócio delegam para esse objeto a responsabilidade de controlar o estado. Simplificando a manutenção e eliminando condicionais no código.
Um exemplo de aplicação do State Pattern é o seguinte: recursos são solicitados para projetos por clientes e aprovados ou recusados por analistas e gestores da empresa. A Figura 1 exibe o diagrama representando o fluxo de estados para a solicitação.

A classe SolicitacaoStatus abstrai um estado da entidade Solicitacao e define a interface dos métodos que o alteram. Ao trocar a instância utilizada na propriedade status, consequentemente altera-se o comportamento da entidade.
public abstract class SolicitacaoStatus implements Serializable { protected Solicitacao solicitacao; public abstract void solicitar() throws SolicitacaoException; public abstract void aprovar() throws SolicitacaoException; public abstract void recusar() throws SolicitacaoException; public abstract void analisar() throws SolicitacaoException; public abstract void encaminhar() throws SolicitacaoException; //... getters and setters }
Na classe Solicitacao, apenas a lógica comum será mantida e o comportamento específico de cada estado estará nas subclasses de SolicitacaoStatus.
public class Solicitacao implements Serializable { private SolicitacaoStatus status = new SolicitacaoStatusEmElaboracao(this); private Usuario solicitante; private Usuario aprovador; private Date dataSolicitacao = new Date(); //... getters and setters public String solicitar() throws SolicitacaoException { // mais ações referentes a solicitação... this.setSolicitante(usuario); this.status.setSolicitacao(this); this.status.solicitar(); return "Solicitação realizada!"; } public String aprovar() throws IncubadoraException { // mais ações referentes a aprovação... this.setAprovador(usuario); this.status.setSolicitacao(this); this.status.aprovar(); return "Solicitação aprovada!"; } public String recusar() throws SolicitacaoException { // mais ações referentes a recusa... this.status.setSolicitacao(this); this.status.recusar(); return "Solicitação recusada!"; } public String analisar() throws SolicitacaoException { // mais ações referentes a analise... this.status.setSolicitacao(this); this.status.analisar(); return "Solicitação analisada!"; } public String encaminhar() throws SolicitacaoException { // mais ações referentes a encaminhamento para gestor... this.status.setSolicitacao(this); this.status.encaminhar(); return "Solicitação encaminhada!"; } }
O estado inicial será uma instância para a subclasse SolicitacaoStatusEmElaboracao.
public class SolicitacaoStatusEmElaboracao extends SolicitacaoStatus { public SolicitacaoStatusEmElaboracao() { super(); } public SolicitacaoStatusEmElaboracao(Solicitacao solicitacao) { this.solicitacao = solicitacao; } @Override public void solicitar() throws SolicitacaoException { getSolicitacao().setStatus(new SolicitacaoStatusAguardandoResposta()); } @Override public void aprovar() throws SolicitacaoException { throw new SolicitacaoException("Não é permitido aprovar!"); } @Override public void recusar() throws SolicitacaoException { throw new SolicitacaoException("Não é permitido recusar!"); } @Override public void analisar() throws SolicitacaoException { throw new SolicitacaoException("Não é permitido analisar!"); } @Override public void encaminhar() throws SolicitacaoException { throw new SolicitacaoException("Não é permitido encaminhar para o gestor!"); } }
Após a elaboração, o estado muda para Aguardando Resposta implementada na classe:
public class SolicitacaoStatusAguardandoResposta extends SolicitacaoStatus { public SolicitacaoStatusAguardandoResposta() { super(); } public SolicitacaoStatusAguardandoResposta(Solicitacao solicitacao) { this.solicitacao = solicitacao; } @Override public void solicitar() throws SolicitacaoException { throw new SolicitacaoException("Não é permitido solicitar recurso!"); } @Override public void aprovar() throws SolicitacaoException { getSolicitacao().setStatus(new SolicitacaoStatusAprovado()); } @Override public void recusar() throws SolicitacaoException { getSolicitacao().setStatus(new SolicitacaoStatusRecusado()); } @Override public void analisar() throws SolicitacaoException { getSolicitacao().setStatus(new SolicitacaoStatusAguardandoAnalise()); } @Override public void encaminhar() throws SolicitacaoException { throw new SolicitacaoException("Não é permitido encaminhar para gestor!"); } }
Ao encaminhar solicitação para um analista, o estado muda para Aguardando Analise:
public class SolicitacaoStatusAguardandoAnalise extends SolicitacaoStatus { public SolicitacaoStatusAguardandoAnalise() { super(); } public SolicitacaoStatusAguardandoAnalise(Solicitacao solicitacao) { this.solicitacao = solicitacao; } @Override public void solicitar() throws SolicitacaoException { throw new SolicitacaoException("Não é permitido solicitar recurso!"); } @Override public void aprovar() throws SolicitacaoException { throw new SolicitacaoException("Não é permitido aprovar!"); } @Override public void analisar() throws SolicitacaoException { getSolicitacao().setStatus(new SolicitacaoStatusAguardandoAnalise()); } @Override public void recusar() throws SolicitacaoException { throw new SolicitacaoException("Não é permitido recusar!"); } @Override public void encaminhar() throws SolicitacaoException { if (solicitacao.getQtdDeAnalisesEmAndamento() <= 1) { solicitacao.setStatus(new SolicitacaoStatusAguardandoResposta()); } } }
O estado muda para Aprovado quando algum analista ou gestor aprova a solicitação:
public class SolicitacaoStatusAprovado extends SolicitacaoStatus { public SolicitacaoStatusAprovado() { super(); } public SolicitacaoStatusAprovado(Solicitacao solicitacao) { this.solicitacao = solicitacao; } @Override public void solicitar() throws SolicitacaoException { throw new SolicitacaoException("Não é permitido solicitar recurso!"); } @Override public void aprovar() throws SolicitacaoException { throw new SolicitacaoException("Não é permitido aprovar!"); } @Override public void recusar() throws SolicitacaoException { throw new SolicitacaoException("Não é permitido recusar!"); } @Override public void analisar() throws SolicitacaoException { throw new SolicitacaoException("Não é permitido analisar!"); } @Override public void encaminhar() throws SolicitacaoException { throw new SolicitacaoException("Não é permitido encaminhar para gestor!"); } }
O estado muda para Recusado quando algum analista ou gestor recusa a solicitação:
public class SolicitacaoStatusRecusado extends SolicitacaoStatus { public SolicitacaoStatusRecusado() { super(); } public SolicitacaoStatusRecusado(Solicitacao solicitacao) { this.solicitacao = solicitacao; } @Override public void solicitar() throws SolicitacaoException { throw new SolicitacaoException("Não é permitido solicitar recurso!"); } @Override public void aprovar() throws SolicitacaoException { throw new SolicitacaoException("Não é permitido aprovar!"); } @Override public void recusar() throws SolicitacaoException { throw new SolicitacaoException("Não é permitido recusar!"); } @Override public void analisar() throws SolicitacaoException { throw new SolicitacaoException("Não é permitido analisar!"); } @Override public void encaminhar() throws SolicitacaoException { throw new SolicitacaoException("Não é permitido encaminhar para gestor!"); } }
É convenção criar a classe que tem o estado quanto os estados no mesmo pacote, assim utilizando o modificador de acesso dos atributos como protected, permite-se que classes vizinhas enxerguem e manipulem seus atributos.
“O conceito do que significa um estado para uma determinada entidade do software pode começar a ficar explícito somente no momento da codificação. A repetição de condicionais similares em diversos pontos da mesma classe pode ser sinal de que seria adequada a refatoração do código em direção ao padrão State.” (GUERRA, 2013)
Segundo Aniche, o controle das possíveis transições são vários e complexos em uma máquina de estados, fazendo com que a implementação não seja simples. O State auxilia a manter o controle dos estados simples e organizados através da criação de classes que representem cada estado e saiba controlar as transições.
Referências e Links
ANICHE, Maurício. Design Patterns Java I: Boas práticas de programação. Curso online na plataforma Alura. Disponível em: https://www.alura.com.br/curso-online-design-patterns.
GUERRA, Eduardo. Design Patterns com Java: Projeto orientado a objetos guiado por padrões. Disponível em: https://www.casadocodigo.com.br/products/livro-design-patterns.