Padrão de Projeto: State (Estado)

Investir em Você é Barra de Ouro a R$ 2,00. Cadastre-se e receba grátis conteúdos Android sem precedentes! Você receberá um email de confirmação. Somente depois de confirma-lo é que eu poderei lhe enviar os conteúdos semanais exclusivos. Os artigos em PDF são entregues somente para os inscritos na lista.

Email inválido.
Blog /Android /Padrão de Projeto: State (Estado)

Padrão de Projeto: State (Estado)

Vinícius Thiengo
(7383)
Go-ahead
"O método consciente de tentativa e erro é mais bem-sucedido que o planejamento de um gênio isolado."
Peter Skillman
Prototipagem Android
Capa do curso Prototipagem Profissional de Aplicativos
TítuloAndroid: Prototipagem Profissional de Aplicativos
CategoriasAndroid, Design, Protótipo
AutorVinícius Thiengo
Vídeo aulas186
Tempo15 horas
ExercíciosSim
CertificadoSim
Acessar Curso
Quer aprender a programar para Android? Acesse abaixo o curso gratuito no Blog.
Lendo
TítuloManual de DevOps: como obter agilidade, confiabilidade e segurança em organizações tecnológicas
CategoriaEngenharia de Software
Autor(es)Gene Kim, Jez Humble, John Willis, Patrick Debois
EditoraAlta Books
Edição1ª
Ano2018
Páginas464
Conteúdo Exclusivo
Investir em Você é Barra de Ouro a R$ 2,00. Cadastre-se e receba gratuitamente conteúdos Android sem precedentes!
Email inválido

Tudo bem?

Neste artigo nós vamos falar sobre o padrão de projeto State (ou Estado).

Este que é pré-requisito para o entendimento do método de refatoração Substituir Condicionais que Alteram Estado por State.

Antes de prosseguir, não esqueça de se inscrever na ðŸ“« lista de e-mails do Blog para receber em primeira mão todos os conteúdos exclusivos sobre desenvolvimento e codificação limpa.

Abaixo os tópicos que estaremos abordando em artigo:

Apresentação

Vamos iniciar visualizando a definição do padrão proposto aqui:

Permitir que um objeto mude de comportamento de acordo com seu estado interno. Parecendo assim que o objeto mudou de tipo, ou seja, é um novo objeto de uma outra classe.

Confuso? No decorrer do post ficará mais claro.

Nesse artigo vamos utilizar como código de exemplo uma classe que representa uma máquina de refrigerantes, ou, logo, a classe MaquinaRefrigerantes.

Primeiro vamos visualizar o diagrama de estados da máquina de refrigerantes, que por sinal não precisa de conhecimentos prévios para o entendimento.

Esse diagrama é bem simples:

Diagrama de estados da máquina de refrigerantes

Note que a máquina de refrigerantes têm até o momento exatos quatro estados, são eles:

  • Sem moeda;
  • Com moeda;
  • Venda;
  • e Sem refrigerantes.

De um estado para o outro há ao menos uma ação, como por exemplo: "Inseriu moeda" que faz com que o estado da máquina de refrigerantes saia de "Sem moeda" para "Com moeda”.

Um possível código para representar a máquina de refrigerantes seria como o abaixo.

Primeiro as variáveis de instância, constantes e construtor da classe MaquinaRefrigerante:

public class MaquinaRefrigerantes {

private static final int ESTADO_SEM_MOEDA = 1;
private static final int ESTADO_COM_MOEDA = 2;
private static final int ESTADO_VENDA = 3;
private static final int ESTADO_SEM_REFRIGERANTE = 4;

private int estado = ESTADO_SEM_REFRIGERANTE;
private int quantidadeRefrigerantes;

public MaquinaRefrigerante( int quantidadeRefrigerantes ){

this.quantidadeRefrigerantes = quantidadeRefrigerantes;

if( quantidadeRefrigerantes > 0 ){

estado = ESTADO_SEM_MOEDA;
}
}

...
}

 

Veja que os estados são nada mais nada menos que constantes não globais (porque são private) do tipo inteiro.

Até parece uma escolha inteligente. No conteúdo do artigo vamos ver o problema dessa abordagem.

Agora os métodos restantes da classe MaquinaRefrigerantes:

public class MaquinaRefrigerantes {
...

public void inseriuMoeda(){

if( estado == ESTADO_SEM_MOEDA ){

System.out.println( "OK: Moeda inserida com sucesso." );
estado = ESTADO_COM_MOEDA;
}
else if( estado == ESTADO_COM_MOEDA ){

System.out.println( "FALHOU: Você já inseriu uma moeda." );
}
else if( estado == ESTADO_VENDA ){

System.out.println( "FALHOU: Aguarde, já estamos liberando seu refrigerante." );
}
else if( estado == ESTADO_SEM_REFRIGERANTE ){

System.out.println( "FALHOU: Não há mais refrigerantes." );
}
}

public void solicitouMoeda(){

if( estado == ESTADO_SEM_MOEDA ){

System.out.println( "FALHOU: Não há mais moedas disponíveis para retorno." );
}
else if( estado == ESTADO_COM_MOEDA ){

System.out.println( "OK: A moeda será devolvida em um segundo." );
estado = ESTADO_SEM_MOEDA;
}
else if( estado == ESTADO_VENDA ){

System.out.println( "FALHOU: A venda já foi concretizada, não é possível devolve-la." );
}
else if( estado == ESTADO_SEM_REFRIGERANTE ){

System.out.println( "FALHOU: Não há mais refrigerantes, logo não foi possível inserir moeda." );
}
}

public void despejarRefrigerante(){

if( estado == ESTADO_SEM_MOEDA ){

System.out.println( "FALHOU: Você ainda não colocou a moeda." );
}
else if( estado == ESTADO_COM_MOEDA ){

System.out.println( "OK: O refrigerante será despejado em um segundo." );
estado = ESTADO_VENDA;
despejar();
}
else if( estado == ESTADO_VENDA ){

System.out.println( "FALHOU: Aguarde, o refrigerante já será liberado." );
}
else if( estado == ESTADO_SEM_REFRIGERANTE ){

System.out.println( "FALHOU: Não há mais refrigerantes, logo não é possível despejar algum." );
}
}

private void despejar(){

quantidadeRefrigerantes--;

if( quantidadeRefrigerantes > 0 ){

estado = ESTADO_SEM_MOEDA;
}
else{

estado = ESTADO_SEM_REFRIGERANTE;
}
}

public void inserirRefrigerantes( int quantidadeRefrigerantes ){

this.quantidadeRefrigerantes = quantidadeRefrigerantes;

if( quantidadeRefrigerantes > 0 ){

estado = ESTADO_SEM_MOEDA;
}
else{

quantidadeRefrigerantes = 0;
}
}
}

 

Note que todos são métodos de transição de estados, vários condicionais estão sendo utilizados, nenhum problema quanto a isso, a solução funciona.

Mas qual é o primeiro indício de que a solução acima não é das melhores? Os vários condicionais utilizados.

Por exemplo, vamos adicionar um novo estado, "Manutenção".

Veja o diagrama de estados quando utilizando também esse novo estado:

Diagrama de estados quando adicionando um novo estado

Com esse novo diagrama teríamos ao menos dois novos métodos, acionarManutencao() e desacionarManutencao().

Além de um novo estado:

public class MaquinaRefrigerantes {
...
private static final int ESTADO_MANUTENCAO = 5;

...

public void acionarManutencao(){

if( estado == ESTADO_SEM_MOEDA
|| estado == ESTADO_SEM_REFRIGERANTE ){

System.out.println( "OK: máquina agora em manutenção." );
estado = ESTADO_MANUTENCAO;
}
else if( estado == ESTADO_COM_MOEDA
|| estado == ESTADO_VENDA ){

System.out.println( "FALHOU: Ainda em processamento, aguarde os estados ociosos para entrar em mnutenção." );
}
else if( estado == ESTADO_MANUTENCAO ){

System.out.println( "FALHOU: Já está em manutenção." );
}
}

public void desacionarManutencao(){

if( estado == ESTADO_SEM_MOEDA
|| estado == ESTADO_SEM_REFRIGERANTE
|| estado == ESTADO_COM_MOEDA
|| estado == ESTADO_VENDA ){

System.out.println( "FALHOU: Máquina não está em mnutenção." );
}
else if( estado == ESTADO_MANUTENCAO ){

System.out.println( "OK: máquina ativa novamente." );

if( quantidadeRefrigerantes > 0 ){

estado = ESTADO_SEM_MOEDA;
}
else{

estado = ESTADO_SEM_REFRIGERANTE;
}
}
}
}

 

E obviamente nossos métodos de mudança de estados seriam todos atualizados (exceto o método despejar()) para comportar o condicional do novo estado, Manutenção:

public class MaquinaRefrigerantes {
...

public void inseriuMoeda(){

if( estado == ESTADO_SEM_MOEDA ){

System.out.println( "OK: Moeda inserida com sucesso." );
estado = ESTADO_COM_MOEDA;
}
else if( estado == ESTADO_COM_MOEDA ){

System.out.println( "FALHOU: Você já inseriu uma moeda." );
}
else if( estado == ESTADO_VENDA ){

System.out.println( "FALHOU: Aguarde, já estamos liberando seu refrigerante." );
}
else if( estado == ESTADO_SEM_REFRIGERANTE ){

System.out.println( "FALHOU: Não há mais refrigerantes." );
}
else if( estado == ESTADO_MANUTENCAO ){

System.out.println( "FALHOU: Máquina em manutenção." );
}
}

public void solicitouMoeda(){

if( estado == ESTADO_SEM_MOEDA ){

System.out.println( "FALHOU: Não há mais moedas disponíveis para retorno." );
}
else if( estado == ESTADO_COM_MOEDA ){

System.out.println( "OK: A moeda será devolvida em um segundo." );
estado = ESTADO_SEM_MOEDA;
}
else if( estado == ESTADO_VENDA ){

System.out.println( "FALHOU: A venda já foi concretizada, não é possível devolve-la." );
}
else if( estado == ESTADO_SEM_REFRIGERANTE ){

System.out.println( "FALHOU: Não há mais refrigerantes, logo não foi possível inserir moeda." );
}
else if( estado == ESTADO_MANUTENCAO ){

System.out.println( "FALHOU: Máquina em manutenção." );
}
}

public void despejarRefrigerante(){

if( estado == ESTADO_SEM_MOEDA ){

System.out.println( "FALHOU: Você ainda não colocou a moeda." );
}
else if( estado == ESTADO_COM_MOEDA ){

System.out.println( "OK: O refrigerante será despejado em um segundo." );
estado = ESTADO_VENDA;
despejar();
}
else if( estado == ESTADO_VENDA ){

System.out.println( "FALHOU: Aguarde, o refrigerante já será liberado." );
}
else if( estado == ESTADO_SEM_REFRIGERANTE ){

System.out.println( "FALHOU: Não há mais refrigerantes, logo não é possível despejar algum." );
}
else if( estado == ESTADO_MANUTENCAO ){

System.out.println( "FALHOU: Máquina em manutenção." );
}
}

...

public void inserirRefrigerantes( int quantidadeRefrigerantes ){
this.quantidadeRefrigerantes = quantidadeRefrigerantes;

if( quantidadeRefrigerantes > 0
&& estado == ESTADO_SEM_REFRIGERANTE ){

estado = ESTADO_SEM_MOEDA;
}
else{

quantidadeRefrigerantes = 0;
}
}

...
}

Note que quando a máquina de refrigerantes estiver nos estados ESTADO_SEM_REFRIGERANTE ou ESTADO_MANUTENCAO, vamos assumir que a moeda.

Assim que colocada pelo cliente, é imediatamente despejada, sem precisar acionar o método solicitouMoeda() ou qualquer outro método para essa ação.

Com a atualização do código anterior temos que o número de condicionais não para de crescer, algo que seguindo os princípios da orientação a objetos é ruim, pois esse código.

Além de ser pouco legível (não passa a intenção dele de maneira simples) é mais passivo na criação de bugs.

Ok, então qual seria a possível solução?

A solução seria um código que permitisse que sejam respeitados ao menos os princípios:

  • "Encapsule o que varia" (os estados do projeto);
  • "Classes devem estar abertas para expansão, mas fechadas para modificação" (quando há um novo estado temos de modificar os métodos ao invés de apenas acrescentar novos);
  • e "Uma classe deve ter somente uma única razão para mudar" (a classe MaquinaRefrigerantes é muito maior do que apenas uma classe com estados).

Por que respeitar ao menos esses princípios e não todos de uma única vez?

Porque nesse código de máquina de refrigerantes esses princípios já seriam o suficiente para manter o projeto limpo e intuitivo.

Obviamente que se você enxergar ainda mais melhorias, mais princípios que podem ser seguidos, implemente-os.

Logo nossa melhor solução nesse caso é implementar o padrão State. Esse padrão vai nos permitir remover a lógica de estados da classe MaquinaRefrigerantes.

Com isso vamos ter classes para cada possível estado da máquina de refrigerantes, colocando mais intenção no código devido ao uso de classes específicas, métodos auto comentados e código encapsulado.

Conseguindo também que essas classes sejam abertas para expansão (mais métodos quando houver mais estados) e fechadas para modificação (a alteração dos métodos existentes pode ocorrer, mas em casos raros).

Diagrama

Antes de prosseguir com a atualização do código vamos ao diagrama do padrão State:

Diagrama do padrão de projeto State

Com ele fica claro que trabalhamos com composição, ou seja, nossas variáveis de estado são variáveis de instância em nossa classe de contexto.

Classe de contexto?

Sim, em nosso caso é a classe MaquinaRefrigerantes.

Note que os métodos de transição de estados, quando invocados na instância da classe de contexto, delegam a chamada para os mesmos métodos da instância da classe de estado.

Código de exemplo

Redesenhando nosso projeto, temos que criar uma nova classe abstrata (ou Interface) que tenha definidos todos os métodos de transação de estados.

Alguns terão uma definição comum, outros serão abstratos deixando a implementação com as subclasses:

abstract public class Estado {

protected MaquinaRefrigerantes maquinaRefrigerantes;

protected Estado( MaquinaRefrigerantes maquinaRefrigerantes ){

this.maquinaRefrigerantes = maquinaRefrigerantes;
}


public void inseriuMoeda(){

System.out.println( "FALHOU: Máquina em manutenção." );
}

public void solicitouMoeda(){

System.out.println( "FALHOU: Máquina em manutenção." );
}

public void despejarRefrigerante(){

System.out.println( "FALHOU: Máquina em manutenção." );
}

protected void despejar(){}

public void inserirRefrigerantes( int quantidadeRefrigerantes ){

int totalRefrigerantes = maquinaRefrigerantes.getQuantidadeRefrigerantes();
totalRefrigerantes += quantidadeRefrigerantes;
maquinaRefrigerantes.setQuantidadeRefrigerantes( totalRefrigerantes );
}

public void acionarManutencao(){

System.out.println( "FALHOU: Ainda em processamento, aguarde os estados ociosos para entrar em mnutenção." );
}

public void desacionarManutencao(){

System.out.println( "FALHOU: Máquina não está em mnutenção." );
}
}

 

Note o construtor e a variável de instância que contém uma referência para um objeto de nossa classe de contexto.

Isso é necessário nesse projeto para facilitar a mudança de estado.

A mudança de estado ocorrerá dentro das classes de estado, mas as instâncias dos estados ficarão dentro do objeto da classe de contexto, MaquinaRefrigerantes.

Optei por utilizar uma classe abstrata ao invés de uma Interface para reduzir ao máximo o número de código duplicado.

Porém você vai notar que no método acionarManutencao() escolhi deixar como comum um código que é menor que o código necessário aos estados ESTADO_SEM_MOEDA e ESTADO_SEM_REFRIGERANTE.

Essa escolha foi feita, pois nesse projeto vou deixar as mudanças de estados somente dentro das subclasses de Estado.

Outro ponto importante é o método inserirRefrigerantes().

Mesmo sabendo que no diagrama não esteja implícito a possibilidade de utilizar essa ação em qualquer estado, aqui optamos por permitir a inserção de refrigerantes em qualquer estado da máquina de refrigerantes.

Em nosso domínio do problema não há algum empecilho ao manter essa abordagem.

Ok, mas por que o método despejar() está vazio ao invés de ser abstrato ou ter uma implementação comum?

Quando chegarmos a definição das classes EstadoComMoeda e EstadoVenda você entenderá o porquê desse vazio no método despejar().

Bom, explicada a classe abstrata Estado podemos começar a definir as subclasses dela.

Começando pela EstadoSemMoeda:

public class EstadoSemMoeda extends Estado {

public EstadoSemMoeda( MaquinaRefrigerantes maquinaRefrigerantes ){

super( maquinaRefrigerantes );
}

@Override
public void inseriuMoeda(){

System.out.println( "OK: Moeda inserida com sucesso." );
maquinaRefrigerantes.setEstado( maquinaRefrigerantes.getEstadoComMoeda() );
}

@Override
public void solicitouMoeda(){

System.out.println( "FALHOU: Não há mais moedas disponíveis para retorno." );
}

@Override
public void despejarRefrigerante(){

System.out.println( "FALHOU: Você ainda não colocou uma moeda." );
}

@Override
public void acionarManutencao(){

System.out.println( "OK: máquina agora em manutenção." );
maquinaRefrigerantes.setEstado( maquinaRefrigerantes.getEstadoManutencao() );
}
}

 

Nada de complexo, somente transferimos as respostas dentro dos condicionais de ESTADO_SEM_MOEDA que estavam em MaquinaRefrigerantes para a classe do código anterior.

Veja que o construtor simplesmente envia a instância de MaquinaRefrigerantes para o construtor da classe pai, Estado, isso para então a variável de instância que permitirá a alteração de estado ser preenchida.

Outro ponto a se notar é a última linha do método acionarManutencao():

...
maquinaRefrigerantes.setEstado( maquinaRefrigerantes.getEstadoManutencao() );
...

 

Já vamos chegar a nova configuração da classe MaquinaRefrigerantes, mas lhe adianto que essa linha de atualização de estado será comum em todas as subclasses de Estado.

Antes de prosseguir, perceba que se formos adicionar qualquer outro estado a máquina de refrigerantes, nós vamos, a princípio, somente adicionar métodos ao invés de modificar condicionais de métodos existentes.

Por que "a princípio"? Será sempre assim, não?

Na verdade não.

Pode ser que você adicione algum estado que seja acionado de acordo com a quantidade de refrigerantes na máquina, por exemplo.

Nesse caso você ainda poderá utilizar os mesmos métodos, porém com um condicional de verificação a mais, dentro de condicionais já existentes.

Prosseguindo, agora com a classe de estado EstadoComMoeda:

public class EstadoComMoeda extends Estado {

public EstadoComMoeda( MaquinaRefrigerantes maquinaRefrigerantes ){

super( maquinaRefrigerantes );
}

@Override
public void inseriuMoeda(){

System.out.println( "FALHOU: Você já inseriu uma moeda." );
}

@Override
public void solicitouMoeda(){

System.out.println( "OK: A moeda será devolvida em um segundo." );
maquinaRefrigerantes.setEstado( maquinaRefrigerantes.getEstadoSemMoeda() );
}

@Override
public void despejarRefrigerante(){

System.out.println( "OK: O refrigerante será despejado em um segundo." );
maquinaRefrigerantes.setEstado( maquinaRefrigerantes.getEstadoVenda() );
maquinaRefrigerantes.getEstadoVenda().despejar();
}
}

 

O procedimento de transferir a lógica de negócio da classe MaquinaRefrigerantes referente ao estado ESTADO_COM_MOEDA para a classe EstadoComMoeda é o mesmo que o explicado para a classe EstadoSemMoeda.

Porém note que aqui, mais precisamente no método despejarRefrigerante(), é invocado também o método despejar().

Esse método é comum somente ao estado ESTADO_VENDA, e devido a isso você deve estar se perguntando:

Por que não colocar o método despejar() dentro somente da classe EstadoVenda ao invés de colocá-lo também na superclasse Estado?

Boa pergunta!

Optei por essa estrutura para que não seja necessária a aplicação de casting na utilização do método despejar() dentro de despejarRefrigerante().

Também para que nos métodos getters de Estado, da nova configuração da classe MaquinaRefrigerantes, seja possível trabalhar apenas com o tipo Estado e não com os tipos específicos dessa classe.

E por último, mas não menos importante.

Nós, developers do projeto, é que temos o controle da utilização do método despejar(), pois o código cliente do sistema utilizará nossa classe de contexto, ou seja, a classe MaquinaRefrigerantes e não nossas classes de estado.

Ou seja, o risco de um código cliente de nosso projeto utilizar por engano nosso método despejar() é zero.

Assim podemos prosseguir com as classes de estado.

Agora a classe EstadoVenda:

public class EstadoVenda extends Estado {

public EstadoVenda( MaquinaRefrigerantes maquinaRefrigerantes ){

super( maquinaRefrigerantes );
}

@Override
public void inseriuMoeda(){

System.out.println( "FALHOU: Aguarde, já estamos liberando seu refrigerante." );
}

@Override
public void solicitouMoeda(){

System.out.println( "FALHOU: A venda já foi concretizada, não é possível devolve-la." );
}

@Override
public void despejarRefrigerante(){

System.out.println( "FALHOU: Aguarde, o refrigerante já será liberado." );
}

protected void despejar(){

int qtd = maquinaRefrigerantes.getQuantidadeRefrigerantes();

qtd--;
maquinaRefrigerantes.setQuantidadeRefrigerantes( qtd );

if( qtd > 0 ){

maquinaRefrigerantes.setEstado( maquinaRefrigerantes.getEstadoSemMoeda() );
}
else{

maquinaRefrigerantes.setEstado( maquinaRefrigerantes.getEstadoSemRefrigerante() );
}
}
}

 

Essa classe dispensa comentários, pois já falamos muito sobre ela na definição da classe EstadoComMoeda.

Somente veja a definição do método despejar(), a única classe que realmente precisa dele.

Agora a classe EstadoSemRefrigerante:

public class EstadoSemRefrigerante extends Estado {

public EstadoSemRefrigerante( MaquinaRefrigerantes maquinaRefrigerantes ){

super( maquinaRefrigerantes );
}

@Override
public void inseriuMoeda(){

System.out.println( "FALHOU: Não há mais refrigerantes." );
}

@Override
public void solicitouMoeda(){

System.out.println( "FALHOU: Não há mais refrigerantes, logo não foi possível inserir moeda." );
}

@Override
public void despejarRefrigerante(){

System.out.println( "FALHOU: Não há mais refrigerantes, logo não é possível despejar algum." );
}

@Override
public void inserirRefrigerantes( int quantidadeRefrigerantes ){

super.inserirRefrigerantes( quantidadeRefrigerantes );

if( maquinaRefrigerantes.getQuantidadeRefrigerantes() > 0 ){
maquinaRefrigerantes.setEstado( maquinaRefrigerantes.getEstadoSemMoeda() );
}
}

@Override
public void acionarManutencao(){

System.out.println( "OK: máquina agora em manutenção." );
maquinaRefrigerantes.setEstado( maquinaRefrigerantes.getEstadoManutencao() );
}
}

 

Veja que em inserirRefrigerante() não há a necessidade de verificação se o estado é ESTADO_SEM_REFRIGERANTE, pois já estamos dentro da classe referente a esse estado.

E para finalizar nossa cadeia de classes de estado.

Vamos a classe EstadoManutencao:

public class EstadoManutencao extends Estado {

public EstadoManutencao( MaquinaRefrigerantes maquinaRefrigerantes ){

super( maquinaRefrigerantes );
}

public void acionarManutencao(){

System.out.println( "FALHOU: Já está em manutenção." );
}

public void desacionarManutencao(){

System.out.println( "OK: Máquina ativa novamente." );

if( maquinaRefrigerantes.getQuantidadeRefrigerantes() > 0 ){

maquinaRefrigerantes.setEstado( maquinaRefrigerantes.getEstadoSemMoeda() );
}
else{

maquinaRefrigerantes.setEstado( maquinaRefrigerantes.getEstadoSemRefrigerante() );
}
}
}

 

Como a maioria dos métodos definidos na superclasse Estado tem o conteúdo padrão referente ao estado ESTADO_MANUTENCAO, essa classe é bem menor do que as outras.

Com isso podemos atualizar toda a classe MaquinaRefrigerantes para trabalhar com as novas classes de estado.

Primeiro vamos as variáveis e ao construtor:

public class MaquinaRefrigerantes {

private Estado estadoSemMoeda;
private Estado estadoComMoeda;
private Estado estadoVenda;
private Estado estadoSemRefrigerante;
private Estado estadoManutencao;

private Estado estado;
private int quantidadeRefrigerantes;

public MaquinaRefrigerantes( int quantidadeRefrigerantes ){

this.setQuantidadeRefrigerantes( quantidadeRefrigerantes );

estadoSemMoeda = new EstadoSemMoeda( this );
estadoComMoeda = new EstadoComMoeda( this );
estadoVenda = new EstadoVenda( this );
estadoSemRefrigerante = new EstadoSemRefrigerante( this );
estadoManutencao = new EstadoManutencao( this );

if( quantidadeRefrigerantes > 0 ){

estado = estadoSemMoeda;
}
else{

estado = estadoSemRefrigerante;
}
}

...
}

 

Note que nossas variáveis de estado não são mais static, isso porque no domínio do problema de máquinas de refrigerantes cada máquina tem seu próprio estado.

Caso você trabalhe com um domínio de problema onde o estado de uma entidade é compartilhado por outras entidades do mesmo tipo dela, então continue trabalhando com as variáveis static.

Porém mude o algoritmo das classes de estado para que a atualização de estado ocorra na classe de contexto, isso devido a possibilidade de mais de uma instância poder alterar o estado atual.

Agora vamos aos métodos de MaquinaRefrigerantes:

public class MaquinaRefrigerantes {
...

public void inseriuMoeda(){

estado.inseriuMoeda();
}

public void solicitouMoeda(){

estado.solicitouMoeda();
}

public void despejarRefrigerante(){

estado.despejarRefrigerante();
}

public void inserirRefrigerantes( int quantidadeRefrigerantes ){

estado.inserirRefrigerantes( quantidadeRefrigerantes );
}

public void acionarManutencao(){

estado.acionarManutencao();
}

public void desacionarManutencao(){

estado.desacionarManutencao();
}

public int getQuantidadeRefrigerantes(){

return quantidadeRefrigerantes;
}

public void setQuantidadeRefrigerantes( int quantidadeRefrigerantes ){

this.quantidadeRefrigerantes = quantidadeRefrigerantes > 0 ? quantidadeRefrigerantes : 0;
}

public void setEstado( Estado estado ){

this.estado = estado;
}

public Estado getEstadoSemMoeda() {

return estadoSemMoeda;
}

public Estado getEstadoComMoeda() {

return estadoComMoeda;
}

public Estado getEstadoVenda() {

return estadoVenda;
}

public Estado getEstadoSemRefrigerante() {

return estadoSemRefrigerante;
}

public Estado getEstadoManutencao() {

return estadoManutencao;
}
}

 

Com isso finalizamos a atualização de nosso projeto de máquina de refrigerantes para utilizar o padrão de projeto State e obter assim um código mais conciso e fácil de manter.

Note que essa atualização não foi um processo de refatoração explícito.

Pois a refatoração tem passos bem definidos para a aplicação do padrão State.

Pontos negativos

  • Muitas classes foram adicionadas ao projeto, algo que enquanto o projeto não continua evoluindo é visto como ruim por inflar o pacote de classes de domínio do problema;
  • Dependendo da quantidade de estados sendo trabalhados em seu projeto, essa implementação do padrão State, apesar simples, pode levar um tempo (nada que não seja recompensado futuramente com um código limpo).

Pontos positivos

  • Código visivelmente mais intuitivo e limpo, permitindo fácil evolução e manutenção;
  • Por utilizar um padrão de projeto o código permite a adoção de uma linguagem universal entre programadores, até porque essa é uma das principais vantagens na utilização de padrões de projeto;
  • A inclusão de um novo estado tende a não quebrar os princípios de orientação a objetos, além de permitir o código evoluir por adição e não por alteração.

Conclusão

O padrão State é de fácil implementação e fácil de enxergar quando ele deve ser utilizado.

Esse é um padrão que mesmo colocando ainda mais códigos em seu projeto trará um benefício necessário com o decorrer da evolução dele.

Atente-se para somente utilizar as entidades de estado não static se seu projeto não trabalha com compartilhamento de estados entre as instâncias de classes de contexto.

Caso contrário pode trabalhar com variáveis static diretamente nas classes de contexto ou até melhor, utilizar o padrão Singleton nas classes de estado.

Como já informado no conteúdo do artigo, padrões de projeto tendem a melhorar o código e criar uma linguagem universal entre os programadores do projeto.

Obviamente que deve sim ser estudada a viabilidade da aplicação do padrão, se ele não vai diminuir a performance do projeto.

No caso do padrão State, na maior parte dos casos, ele mais traz benefícios do que pontos negativos.

Então é isso.

Por fim, não deixe de se inscrever na 📩 lista de e-mails do Blog para receber os conteúdos de desenvolvimento e codificação limpa exclusivos... e em primeira mão.

Abraço.

Fontes

Use a Cabeça! Padrões de Projetos

Refatoração Para Padrões

Investir em Você é Barra de Ouro a R$ 2,00. Cadastre-se e receba grátis conteúdos Android sem precedentes!
Email inválido

Relacionado

Padrão de Projeto: ObserverPadrão de Projeto: ObserverAndroid
Padrão de Projeto: Factory MethodPadrão de Projeto: Factory MethodAndroid
Padrão de Projeto: AdapterPadrão de Projeto: AdapterAndroid
Padrão de Projeto: Objeto NuloPadrão de Projeto: Objeto NuloAndroid

Compartilhar

Comentários Facebook

Comentários Blog

Para código / script, coloque entre [code] e [/code] para receber marcação especifica.
Forneça seu nome válido.
Forneça seu email válido.
Forneça o comentário.
Enviando, aguarde...