Refatoração de Código: Substituir Condicionais que Alteram Estado por State

Receba em primeira mão, e com prioridade, os conteúdos Android exclusivos do Blog. Você receberá um email de confirmação. Somente depois de confirma-lo é que poderei lhe enviar os conteúdos exclusivos.

Email inválido.
Blog /Android /Refatoração de Código: Substituir Condicionais que Alteram Estado por State

Refatoração de Código: Substituir Condicionais que Alteram Estado por State

Vinícius Thiengo
(998)
Go-ahead
"A única coisa pior do que não ler um livro nos últimos noventa dias é não está lendo um livro nos últimos noventa dias e pensar que isso não importa."
Jim Rohn
Kotlin Android
Capa do livro Desenvolvedor Kotlin Android - Bibliotecas para o dia a dia
TítuloDesenvolvedor Kotlin Android - Bibliotecas para o dia a dia
CategoriasAndroid, Kotlin
AutorVinícius Thiengo
Edição
Capítulos19
Páginas1035
Acessar Livro
Treinamento Oficial
Android: Prototipagem Profissional de Aplicativos
CursoAndroid: Prototipagem Profissional de Aplicativos
CategoriaAndroid
InstrutorVinícius Thiengo
NívelTodos os níveis
Vídeo aulas186
PlataformaUdemy
Acessar Curso
Receitas Android
Capa do livro Receitas Para Desenvolvedores Android
TítuloReceitas Para Desenvolvedores Android
CategoriaDesenvolvimento Android
AutorVinícius Thiengo
Edição
Ano2017
Capítulos20
Páginas936
Acessar Livro
Código Limpo
Capa do livro Refatorando Para Programas Limpos
TítuloRefatorando Para Programas Limpos
CategoriaEngenharia de Software
AutorVinícius Thiengo
Edição
Ano2017
Capítulos46
Páginas599
Acessar Livro
Quer aprender a programar para Android? Acesse abaixo o curso gratuito no Blog.
Conteúdo Exclusivo
Receba em primeira mão, e com prioridade, os conteúdos Android exclusivos do Blog.
Email inválido

Opa, blz?

Nesse artigo continuamos com a série Refatoração de Código, com o propósito de obtermos códigos mais eficientes.

Dessa vez vamos estar falando sobre o método de refatoração Substituir Condicionais que Alteram Estado por State. Método de refatoração que tem como pré-requisito o conhecimento do padrão de projeto State.

Abaixo deixo o link do artigo aqui do Blog onde somente esse padrão é o assunto:

É muito importante que você tenha em mente a definição do padrão de projeto acima, por isso vou estar repetindo ela 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.

Digo isso, pois a versão do padrão State que estaremos utilizando nesse artigo é a versão que permite que as classes de estado sejam compartilhadas por diferentes instâncias de classes de contexto.

Antes de prosseguir ressalto que os métodos de refatoração dessa série podem ser utilizados em qualquer software orientado a objetos, independente da linguagem de programação.

Tópicos presentes no artigo:

Motivação

Você tem um projeto de software onde ao menos uma das classes altera de comportamento de acordo com o estado interno atual dela.

A definição do estado atual é realizada de acordo com as chamadas de métodos dessa classe, métodos que representam ações no domínio do problema da classe.

Até o momento que a classe tinha a possibilidade de transitar entre poucos estados não se enxergou a necessidade de trocar as lógicas condicionais por um algoritmo mais sofisticado.

Porém com a evolução do sistema, mais estados foram adicionados. Dessa forma a quantidade de condicionais fez com que a classe que contém os estados perdesse legibilidade e a evolução dela se tornou mais dificultada.

Dentro desse contexto, o trabalho com classes de estado, uma provável solução é a refatoração para o padrão State, resultando em um código mais legível e melhor particionado.

Mesmo aumentando o número de classes no domínio do problema do projeto, aplicar a refatoração para o padrão State trará mais benefícios do que pontos negativos.

Código de exemplo

O código de exemplo é referente a um software de segurança que antes de permitir o acesso geral ao sistema, o usuário precisa de permissão concedida pelo banco de dados local ou pelo sistema UNIX, ou seja, ambas as permissões concedidas ou apenas uma delas já é o suficiente para que o usuário acesse todo o software.

Antes de apresentar o código vamos visualizar o diagrama de estados da classe SistemaPermissao:

Aparentemente complicado. Só aparentemente, pois na verdade é simples, o complicado começa quando se utiliza vários condicionais para escolher o estado correto.

Segue código inicial da classe SistemaPermissao, começando pelas declarações de variáveis, constantes e o construtor:

public class SistemaPermissao {
public final static String REQUISITADO = "REQUISITADO";
public final static String REIVINDICADO = "REIVINDICADO";
public final static String CONCEDIDO = "CONCEDIDO";
public final static String NEGADO = "NEGADO";
public final static String UNIX_REQUISITADO = "UNIX_REQUISITADO";
public final static String UNIX_REIVINDICADO = "UNIX_REIVINDICADO";

private SistemaPerfil perfil;
private SistemaUser requisitor;
private SistemaAdmin admin;
private boolean ehConcedido;
private boolean ehUnixConcedido;
private String estado;

public SistemaPermissao( SistemaUser requisitor, SistemaPerfil perfil ){
this.requisitor = requisitor;
this.perfil = perfil;
estado = REQUISITADO;
ehConcedido = false;
ehUnixConcedido = false;
notificaAdminDaPermissaoRequisitada();
}

...
}

 

Lembrando que a classe SistemaPermissao é a classe de contexto de nosso exemplo. Agora vamos aos métodos que permitem a transição entre estados:

public class SistemaPermissao {
...

public void reivindicadoPor( SistemaAdmin admin ){
if( !estado.equals( REQUISITADO ) && !estado.equals( UNIX_REQUISITADO ) ){
return;
}

vaiSerTrabalhadoPor( admin );

if( estado.equals( REQUISITADO ) ){
estado = REIVINDICADO;
}
else if( estado.equals( UNIX_REQUISITADO ) ){
estado = UNIX_REIVINDICADO;
}
}

public void negadoPor( SistemaAdmin admin ){
if( (!estado.equals( REIVINDICADO ) && !estado.equals( UNIX_REIVINDICADO ))
|| !this.admin.equals(admin) ){
return;
}

ehConcedido = false;
ehUnixConcedido = false;
estado = NEGADO;
notificaAdminResultadoDaPermissaoRequisitada();
}

public void concedidoPor( SistemaAdmin admin ){
if( (!estado.equals( REIVINDICADO ) && !estado.equals( UNIX_REIVINDICADO ))
|| !this.admin.equals(admin) ){
return;
}

if( perfil.ehPermissaoUnixRequisitada() && estado.equals( UNIX_REIVINDICADO ) ){
ehUnixConcedido = true;
}
else if( perfil.ehPermissaoUnixRequisitada() && !ehUnixConcedido ){
estado = UNIX_REQUISITADO;
notificaAdminUnixDaPermissaoRequisitada();
return;
}

estado = CONCEDIDO;
ehConcedido = true;
notificaAdminResultadoDaPermissaoRequisitada();
}
}

 

Para não complicar ainda mais a leitura do exemplo não vamos visualizar os outros métodos de SistemaPermissao, somente os que permitem transição entre estados e os realmente necessários de acordo com a refatoração que iremos realizar.

Já podemos começar a aplicar o método de refatoração proposto aqui.

Mecânica

Nosso primeiro passo utilizando o método de refatoração é identificar na classe de contexto, SistemaPermissao, qual variável de instância é responsável por guardar o estado atual dela. Em nosso código de exemplo a variável é a estado.

Identificada a variável, o que devemos fazer, ainda no passo um, é aplicar o método de refatoração Substituir Código de Tipo por Classe, dessa forma teremos uma nova classe que chamaremos de Estado, segue código:

public class Estado {
public static final Estado REQUISITADO = new Estado("REQUISITADO");
public static final Estado REIVINDICADO = new Estado("REIVINDICADO");
public static final Estado CONCEDIDO = new Estado("CONCEDIDO");
public static final Estado NEGADO = new Estado("NEGADO");
public static final Estado UNIX_REQUISITADO = new Estado("UNIX_REQUISITADO");
public static final Estado UNIX_REIVINDICADO = new Estado("UNIX_REIVINDICADO");

private String nome;

private Estado( String nome ){
this.nome = nome;
}

public String toString(){
return nome;
}
}

 

Veja que os estados estão declarados dentro da classe Estado, algo completamente diferente do que foi abordado no código do artigo sobre o padrão de projeto State.

Lembre que aquela forma do padrão State é a que não permite compartilhamento de objetos de estado entre instâncias de classe de contexto. O exemplo aqui permite esse tipo de compartilhamento, mas o princípio do padrão State ainda é o mesmo.

Note o método toString(). Esse método será necessário para permitir a comparação de estados até certo ponto, ao final da refatoração ele poderá ser descartado.

Notou o private como modificador de acesso do construtor de Estado?

Sim, é isso mesmo, a princípio somente as instanciações internas para inicializar as variáveis de estado é que serão permitidas. No passo dois vamos bloquear qualquer tipo de instanciação direta da classe Estado.

Antes de prosseguir para o segundo passo, ainda temos de atualizar o código da classe de contexto, SistemaPermissao. Primeiro as variáveis, constantes, construtor e os novos métodos, setEstado() e getEstado():

public class SistemaPermissao {
/* NÃO HÁ MAIS AS CONSTANTES, ESTÃO TODAS NA CLASSE Estado */

private SistemaPerfil perfil;
private SistemaUser requisitor;
private SistemaAdmin admin;
private boolean ehConcedido;
private boolean ehUnixConcedido;
private Estado estado; /* TIPO ALTERADO PARA ESTADO */

public SistemaPermissao( SistemaUser requisitor, SistemaPerfil perfil ){
this.requisitor = requisitor;
this.perfil = perfil;
setEstado( Estado.REQUISITADO ); /* AGORA UTILIZAMOS MÉTODO setEstado() */
ehConcedido = false;
ehUnixConcedido = false;
notificaAdminDaPermissaoRequisitada();
}

...

/* AMBOS OS MÉTODOS ABAIXO, NESSE INÍCIO, SÃO OPCIONAIS, MAS PERMITEM MENOR VERBOSIDADE NO CÓDIGO */

private void setEstado( Estado estado ){
this.estado = estado;
}

private Estado getEstado(){
return estado;
}
}

 

E então os métodos que permitem alteração de estado, ainda na classe de contexto:

public class SistemaPermissao {
...

public void reivindicadoPor( SistemaAdmin admin ){

if( !getEstado().equals( Estado.REQUISITADO )
&& !getEstado().equals( Estado.UNIX_REQUISITADO ) ){
return;
}

vaiSerTrabalhadoPor( admin );

if( getEstado().equals( Estado.REQUISITADO ) ){
setEstado( Estado.REIVINDICADO );
}
else if( getEstado().equals( Estado.UNIX_REQUISITADO ) ){
setEstado( Estado.UNIX_REIVINDICADO );
}
}

public void negadoPor( SistemaAdmin admin ){

if( (!getEstado().equals( Estado.REIVINDICADO )
&& !getEstado().equals( Estado.UNIX_REIVINDICADO ))
|| !this.admin.equals(admin) ){
return;
}

ehConcedido = false;
ehUnixConcedido = false;
setEstado( Estado.NEGADO );
notificaAdminResultadoDaPermissaoRequisitada();
}

public void concedidoPor( SistemaAdmin admin ){

if( (!getEstado().equals( Estado.REIVINDICADO )
&& !getEstado().equals( Estado.UNIX_REIVINDICADO ))
|| !this.admin.equals(admin) ){
return;
}

if( perfil.ehPermissaoUnixRequisitada()
&& getEstado().equals( Estado.UNIX_REIVINDICADO ) ){
ehUnixConcedido = true;
}
else if( perfil.ehPermissaoUnixRequisitada() && !ehUnixConcedido ){
setEstado( Estado.UNIX_REQUISITADO );
notificaAdminUnixDaPermissaoRequisitada();
return;
}

setEstado( Estado.CONCEDIDO );
ehConcedido = true;
notificaAdminResultadoDaPermissaoRequisitada();
}

...
}

 

Agora seguramente podemos prosseguir para o passo dois. Temos seis constantes na classe Estado, o que precisamos fazer é criar seis subclasses de Estado que representem essas constantes.

Vamos aplicar um simples método de refatoração chamado Extrair Subclasse.

Nossa primeira subclasse será EstadoRequisitado:

public class EstadoRequisitado extends Estado {
public EstadoRequisitado(){
super("REQUISITADO");
}
}

 

Agora a subclasse EstadoReivindicado:

public class EstadoReivindicado extends Estado {
public EstadoReivindicado(){
super("REIVINDICADO");
}
}

 

A subclasse EstadoNegado:

public class EstadoNegado extends Estado {
public EstadoNegado(){
super("NEGADO");
}
}

 

A subclasse EstadoConcedido:

public class EstadoConcedido extends Estado {
public EstadoConcedido(){
super("CONCEDIDO");
}
}

 

A subclasse EstadoUnixRequisitado:

public class EstadoUnixRequisitado extends Estado {
public EstadoUnixRequisitado(){
super("UNIX_REQUISITADO");
}
}

 

E por fim a subclasse EstadoUnixReivindicado:

public class EstadoUnixReivindicado extends Estado {
public EstadoUnixReivindicado(){
super("UNIX_REIVINDICADO");
}
}

 

Com isso devemos alterar os tipos das instâncias das constantes da superclasse Estado, além de alterar a superclasse para abstract para evitar instanciações explícitas diretamente dela.

public abstract class Estado {
public static final EstadoRequisitado REQUISITADO = new EstadoRequisitado();
public static final EstadoReivindicado REIVINDICADO = new EstadoReivindicado();
public static final EstadoConcedido CONCEDIDO = new EstadoConcedido();
public static final EstadoNegado NEGADO = new EstadoNegado();
public static final EstadoUnixRequisitado UNIX_REQUISITADO = new EstadoUnixRequisitado();
public static final EstadoUnixReivindicado UNIX_REIVINDICADO = new EstadoUnixReivindicado();

private String nome;

protected Estado( String nome ){
this.nome = nome;
}

public String toString(){
return nome;
}
}

 

Veja que também é necessário alterar o modificador de acesso do construtor da superclasse Estado.

Por que não coloca todos os tipos de acesso como public desde o início?

Porque essa é uma prática ruim, digo, colocar o acesso muito mais abrangente do que realmente é necessário. Quanto mais restritivo o tipo de acesso de suas entidades, mais propício a evolução seu código é, pois a refatoração das entidades com tipos de acesso mais restritivos é mais simples devido ao escopo de atuação ser menor.

Os métodos setEstado() e getEstado() são private por ainda serem utilizados somente dentro da classe deles, se precisarmos acessá-los em escopos maiores iremos alterar o tipo de acesso para menos restritivo (spoiler: nós vamos realizar essa alteração).

O tipo de acesso protected se encaixa perfeitamente no construtor de Estado, public seria desnecessário e atrapalharia outros programadores a evoluirem o código com maior confiança, sabendo que o escopo seria todo o projeto.

No passo três devemos encontrar os métodos de nossa classe de contexto, SistemaPermissao, que realizam a mudança de estado. Em nosso projeto temos três métodos: reivindicadoPor(), negadoPor() e concedidoPor().

Encontrados os métodos, devemos copiá-los para a classe Estado e realizar as alterações necessárias para que o projeto continue compilando. Segue código de Estado atualizado:

public abstract class Estado {
...

public void reivindicadoPor( SistemaAdmin admin, SistemaPermissao permissao ){
if( !permissao.getEstado().equals( REQUISITADO )
&& !permissao.getEstado().equals( UNIX_REQUISITADO ) ){
return;
}

permissao.vaiSerTrabalhadoPor( admin );

if( permissao.getEstado().equals( REQUISITADO ) ){
permissao.setEstado( REIVINDICADO );
}
else if( permissao.getEstado().equals( UNIX_REQUISITADO ) ){
permissao.setEstado( UNIX_REIVINDICADO );
}
}

public void negadoPor( SistemaAdmin admin, SistemaPermissao permissao ){
if( (!permissao.getEstado().equals( REIVINDICADO )
&& !permissao.getEstado().equals( UNIX_REIVINDICADO ))
|| !this.admin.equals(admin) ){
return;
}

permissao.setEhConcedido( false );
permissao.setEhUnixConcedido( false );
permissao.setEstado( NEGADO );
permissao.notificaAdminResultadoDaPermissaoRequisitada();
}

public void concedidoPor( SistemaAdmin admin, SistemaPermissao permissao ){
if( (!permissao.getEstado().equals( REIVINDICADO )
&& !permissao.getEstado().equals( UNIX_REIVINDICADO ))
|| !this.admin.equals(admin) ){
return;
}

if( permissao.getPerfil().ehPermissaoUnixRequisitada()
&& permissao.getEstado().equals( UNIX_REIVINDICADO ) ){
permissao.setEhUnixConcedido( true );
}
else if( permissao.getPerfil().ehPermissaoUnixRequisitada()
&& !permissao.ehUnixConcedido() ){
permissao.setEstado( UNIX_REQUISITADO );
permissao.notificaAdminUnixDaPermissaoRequisitada();
return;
}

permissao.setEstado( CONCEDIDO );
permissao.setEhConcedido( true );
permissao.notificaAdminResultadoDaPermissaoRequisitada();
}
}

 

Devemos atualizar também a classe SistemaPermissao, primeiro alterando os tipos de acesso dos métodos setEstado() e getEstado() que eram private por serem utilizados somente dentro da classe deles. Altere para public:

public class SistemaPermissao {
...

public void setEstado( Estado estado ){
this.estado = estado;
}

public Estado getEstado(){
return estado;
}
}

 

Agora os métodos de transição de estados de SistemaPermissao, todos devem invocar os mesmos métodos na instância de Estado:

public class SistemaPermissao {
...

public void requisitadoPor( SistemaAdmin admin ){
estado.requisitadoPor(admin, this);
}

public void negadoPor( SistemaAdmin admin ){
estado.negadoPor(admin, this);
}

public void concedidoPor( SistemaAdmin admin ){
estado.concedidoPor(admin, this);
}

...
}

 

E por fim devemos adicionar os métodos setEhConcedido(), setEhUnixConcedido(), ehUnixConcedido() e getPerfil():

public class SistemaPermissao {
...

public void setEhConcedido( boolean ehConcedido ){
this.ehConcedido = ehConcedido;
}

public void setEhUnixConcedido( boolean ehUnixConcedido ){
this.ehUnixConcedido = ehUnixConcedido;
}

public boolean ehUnixConcedido(){
return ehUnixConcedido;
}

public SistemaPerfil getPerfil(){
return perfil;
}
}

 

Com isso podemos prosseguir para o passo quatro. Devemos de forma aleatória escolher um estado, dos seis que temos, e então adicionar a classe desse estado um método de transição entre estados que está em nossa superclasse Estado e que parte do estado que escolhemos para um outro estado do projeto.

Confuso, não? Com o código abaixo ficará mais claro.

Vamos começar com o estado EstadoRequisitado. Temos somente um método de transição que parte desse estado para outro, o método reivindicadoPor(). Atualizando essa classe temos:

public class EstadoRequisitado extends Estado {
...

public void reivindicadoPor( SistemaAdmin admin, SistemaPermissao permissao ){

if( !permissao.getEstado().equals( REQUISITADO )
&& !permissao.getEstado().equals( UNIX_REQUISITADO ) ){
return;
}

permissao.vaiSerTrabalhadoPor( admin );

if( permissao.getEstado().equals( REQUISITADO ) ){
permissao.setEstado( REIVINDICADO );
}
else if( permissao.getEstado().equals( UNIX_REQUISITADO ) ){
permissao.setEstado( UNIX_REIVINDICADO );
}
}
}

 

Observação: nessa classe e em todas as outras vou omitir códigos já apresentados, isso para não complicar o entendimento dos passos.

Vamos refatorar essa classe ainda mais. Começando com o primeiro condicional do método adicionado. Ele é falso quando o estado é REQUISITADO ou UNIX_REQUISITADO. Esse condicional e o corpo dele devem ser removidos por completo, tendo em mente que já estamos dentro do estado EstadoRequisitado. Atualizando, temos:

public class EstadoRequisitado extends Estado {
...

public void reivindicadoPor( SistemaAdmin admin, SistemaPermissao permissao ){
permissao.vaiSerTrabalhadoPor( admin );

if( permissao.getEstado().equals( REQUISITADO ) ){
permissao.setEstado( REIVINDICADO );
}
else if( permissao.getEstado().equals( UNIX_REQUISITADO ) ){
permissao.setEstado( UNIX_REIVINDICADO );
}
}
}

 

Feito isso temos que o método setEstado() do parâmetro permissao do atual primeiro condicional do método reivindicadoPor() somente é chamado quando o estado atual é REQUISITADO, logo devemos remover o condicional e diretamente já permitir a invocação desse método, isso porque já estamos dentro desse estado. Atualizando, temos:

public class EstadoRequisitado extends Estado {
...

public void reivindicadoPor( SistemaAdmin admin, SistemaPermissao permissao ){
permissao.vaiSerTrabalhadoPor( admin );
permissao.setEstado( REIVINDICADO );

if( permissao.getEstado().equals( UNIX_REQUISITADO ) ){
permissao.setEstado( UNIX_REIVINDICADO );
}
}
}

 

E agora seguramente podemos remover o condicional e corpo relacionado ao estado UNIX_REQUISITADO, pois esse estado tem a própria classe dele, lá é que vamos utilizar esses códigos. Com isso, agora temos a classe EstadoRequisitado totalmente refatorada:

public class EstadoRequisitado extends Estado {
...

public void reivindicadoPor( SistemaAdmin admin, SistemaPermissao permissao ){
permissao.vaiSerTrabalhadoPor( admin );
permissao.setEstado( REIVINDICADO );
}
}

 

Note que ainda no passo quatro vamos refatorar todas as outras subclasses.

Agora a subclasse EstadoReivindicado. Os métodos em Estado que permitem sair de REIVINDICADO para outros estados são: negadoPor() e concedidoPor(). Atualizando essa classe temos:

public class EstadoReivindicado extends Estado {
...

public void negadoPor( SistemaAdmin admin, SistemaPermissao permissao ){

if( (!permissao.getEstado().equals( REIVINDICADO )
&& !permissao.getEstado().equals( UNIX_REIVINDICADO ))
|| !this.admin.equals(admin) ){
return;
}

permissao.setEhConcedido( false );
permissao.setEhUnixConcedido( false );
permissao.setEstado( NEGADO );
permissao.notificaAdminResultadoDaPermissaoRequisitada();
}

public void concedidoPor( SistemaAdmin admin, SistemaPermissao permissao ){

if( (!permissao.getEstado().equals( REIVINDICADO )
&& !permissao.getEstado().equals( UNIX_REIVINDICADO ))
|| !this.admin.equals(admin) ){
return;
}

if( permissao.getPerfil().ehPermissaoUnixRequisitada()
&& permissao.getEstado().equals( UNIX_REIVINDICADO ) ){
permissao.setEhUnixConcedido( true );
}
else if( permissao.getPerfil().ehPermissaoUnixRequisitada()
&& !permissao.ehUnixConcedido() ){
permissao.setEstado( UNIX_REQUISITADO );
permissao.notificaAdminUnixDaPermissaoRequisitada();
return;
}

permissao.setEstado( CONCEDIDO );
permissao.setEhConcedido( true );
permissao.notificaAdminResultadoDaPermissaoRequisitada();
}
}

 

Nosso primeiro ato de refatoração nessa classe é remover os condicionais iniciais de ambos os métodos que verificam, além do administrador, se o estado não é REIVINDICADO e nem UNIX_REIVINDICADO. Como já estamos na classe de estado EstadoReivindicado, essas verificações são desnecessárias. Então agora temos:

public class EstadoReivindicado extends Estado {
...

public void negadoPor( SistemaAdmin admin, SistemaPermissao permissao ){
permissao.setEhConcedido( false );
permissao.setEhUnixConcedido( false );
permissao.setEstado( NEGADO );
permissao.notificaAdminResultadoDaPermissaoRequisitada();
}

public void concedidoPor( SistemaAdmin admin, SistemaPermissao permissao ){
if( permissao.getPerfil().ehPermissaoUnixRequisitada()
&& permissao.getEstado().equals( UNIX_REIVINDICADO ) ){
permissao.setEhUnixConcedido( true );
}
else if( permissao.getPerfil().ehPermissaoUnixRequisitada()
&& !permissao.ehUnixConcedido() ){
permissao.setEstado( UNIX_REQUISITADO );
permissao.notificaAdminUnixDaPermissaoRequisitada();
return;
}

permissao.setEstado( CONCEDIDO );
permissao.setEhConcedido( true );
permissao.notificaAdminResultadoDaPermissaoRequisitada();
}
}

 

Logo depois removemos os condicionais que dependem exclusivamente a outros estados do sistema. Nesse caso somente o método concedidoPor() será alterado. Mais precisamente vamos remover o primeiro condicional dele:

public class EstadoReivindicado extends Estado {
...

public void concedidoPor( SistemaAdmin admin, SistemaPermissao permissao ){
if( permissao.getPerfil().ehPermissaoUnixRequisitada()
&& !permissao.ehUnixConcedido() ){
permissao.setEstado( UNIX_REQUISITADO );
permissao.notificaAdminUnixDaPermissaoRequisitada();
return;
}

permissao.setEstado( CONCEDIDO );
permissao.setEhConcedido( true );
permissao.notificaAdminResultadoDaPermissaoRequisitada();
}
}

 

Então você deve estar se perguntando: por que não remover também o segundo condicional, pois ele depende de uma verificação ao estado UNIX?

Na verdade essa verificação é realizada a partir da entidade SistemaPerfil via getPerfil(), método de SistemaPermissao, ou seja, não é o mesmo estado que o de SistemaPermissao, esse já é o estado de outra entidade. Então esse condicional deve permanecer ali.

Agora vamos a classe de estado EstadoUnixRequisitado. Vamos novamente copiar o método reivindicadoPor() da classe Estado, pois esse é o método que também sai do estado UNIX_REQUISITADO para outro estado do sistema. Segue  atualização em EstadoUnixRequisitado:

public class EstadoUnixRequisitado extends Estado {
...

public void reivindicadoPor( SistemaAdmin admin, SistemaPermissao permissao ){

if( !permissao.getEstado().equals( REQUISITADO )
&& !permissao.getEstado().equals( UNIX_REQUISITADO ) ){
return;
}

permissao.vaiSerTrabalhadoPor( admin );

if( permissao.getEstado().equals( REQUISITADO ) ){
permissao.setEstado( REIVINDICADO );
}
else if( permissao.getEstado().equals( UNIX_REQUISITADO ) ){
permissao.setEstado( UNIX_REIVINDICADO );
}
}
}

 

Começamos removendo o primeiro condicional que impede o processamento do código abaixo dele, caso o estado não seja REQUISITADO e nem UNIX_REQUISITADO. Já estamos na classe EstadoUnixRequisitado, logo esse condicional é inútil:

public class EstadoUnixRequisitado extends Estado {
...

public void reivindicadoPor( SistemaAdmin admin, SistemaPermissao permissao ){
permissao.vaiSerTrabalhadoPor( admin );

if( permissao.getEstado().equals( REQUISITADO ) ){
permissao.setEstado( REIVINDICADO );
}
else if( permissao.getEstado().equals( UNIX_REQUISITADO ) ){
permissao.setEstado( UNIX_REIVINDICADO );
}
}
}

 

Agora podemos seguramente remover o condicional e corpo referente ao estado REQUISITADO, pois esse estado já tem sua própria classe. E, aproveitando, vamos remover o condicional, mas não o corpo, que verifica se estamos com o estado UNIX_REQUISITADO, pois estamos nesse estado. Atualizando, temos:

public class EstadoUnixRequisitado extends Estado {
...

public void reivindicadoPor( SistemaAdmin admin, SistemaPermissao permissao ){
permissao.vaiSerTrabalhadoPor( admin );
permissao.setEstado( UNIX_REIVINDICADO );
}
}

 

Agora vamos a classe EstadoUnixReivindicado, que nesse projeto é muito similar a classe EstadoReivindicado. Logo vamos copiar o conteúdo dessa classe para EstadoUnixReivindicado. Assim e temos:

public class EstadoUnixReivindicado extends Estado {
...

public void negadoPor( SistemaAdmin admin, SistemaPermissao permissao ){
permissao.setEhConcedido( false );
permissao.setEhUnixConcedido( false );
permissao.setEstado( NEGADO );
permissao.notificaAdminResultadoDaPermissaoRequisitada();
}

public void concedidoPor( SistemaAdmin admin, SistemaPermissao permissao ){

if( permissao.getPerfil().ehPermissaoUnixRequisitada()
&& !permissao.ehUnixConcedido() ){
permissao.setEstado( UNIX_REQUISITADO );
permissao.notificaAdminUnixDaPermissaoRequisitada();
return;
}

permissao.setEstado( CONCEDIDO );
permissao.setEhConcedido( true );
permissao.notificaAdminResultadoDaPermissaoRequisitada();
}
}

 

O que devemos fazer é voltar com o condicional antes do condicional restante de concedidoPor(), que foi removido na refatoração da classe EstadoReivindicado. Atualizando, temos:

public class EstadoUnixReivindicado extends Estado {
...

public void concedidoPor( SistemaAdmin admin, SistemaPermissao permissao ){
if( permissao.getPerfil().ehPermissaoUnixRequisitada()
&& permissao.getEstado().equals( UNIX_REIVINDICADO ) ){
permissao.setEhUnixConcedido( true );
}
else if( permissao.getPerfil().ehPermissaoUnixRequisitada()
&& !permissao.ehUnixConcedido() ){
permissao.setEstado( UNIX_REQUISITADO );
permissao.notificaAdminUnixDaPermissaoRequisitada();
return;
}

permissao.setEstado( CONCEDIDO );
permissao.setEhConcedido( true );
permissao.notificaAdminResultadoDaPermissaoRequisitada();
}
}

 

Note que não há necessidade de manter no primeiro condicional a verificação do estado UNIX_REIVINDICADO, pois já estamos nele. Note que não é para remover o condicional, somente uma das verificações presentes nele. Logo, temos:

public class EstadoUnixReivindicado extends Estado {
...

public void concedidoPor( SistemaAdmin admin, SistemaPermissao permissao ){
if( permissao.getPerfil().ehPermissaoUnixRequisitada() ){
permissao.setEhUnixConcedido( true );
}
else if( permissao.getPerfil().ehPermissaoUnixRequisitada()
&& !permissao.ehUnixConcedido() ){
permissao.setEstado( UNIX_REQUISITADO );
permissao.notificaAdminUnixDaPermissaoRequisitada();
return;
}

permissao.setEstado( CONCEDIDO );
permissao.setEhConcedido( true );
permissao.notificaAdminResultadoDaPermissaoRequisitada();
}
}

 

Agora restam ainda as classes EstadoNegado e EstadoConcedido. E adivinhe? Isso mesmo. Esses são estados finais das instâncias de SistemaPermissao, ou seja, essas classes herdarão o comportamento dos métodos reivindicadoPor(), negadoPor() e concedidoPor() da superclasse Estado.

No passo cinco vamos atualizar a classe Estado. Tendo em mente que os processamentos dos métodos dessa classe já foram alocados para suas classes específicas, podemos esvaziar os métodos de Estado e deixar a classe como abaixo:

public abstract class EstadoSistemaPermissao {
public static final EstadoRequisitado REQUISITADO = new EstadoRequisitado();
public static final EstadoReivindicado REIVINDICADO = new EstadoReivindicado();
public static final EstadoConcedido CONCEDIDO = new EstadoConcedido();
public static final EstadoNegado NEGADO = new EstadoNegado();
public static final EstadoUnixRequisitado UNIX_REQUISITADO = new EstadoUnixRequisitado();
public static final EstadoUnixReivindicado UNIX_REIVINDICADO = new EstadoUnixReivindicado();

private String nome;

protected EstadoSistemaPermissao( String nome ){
this.nome = nome;
}

public String toString(){
return nome;
}


public void reivindicadoPor( SistemaAdmin admin, SistemaPermissao permissao ){}

public void negadoPor( SistemaAdmin admin, SistemaPermissao permissao ){}

public void concedidoPor( SistemaAdmin admin, SistemaPermissao permissao ){}
}

 

Com isso finalizamos nossa refatoração para o padrão State utilizando o método Substituir Condicionais que Alteram Estado por State.

Assim conseguimos um código com uma divisão mais sofisticada e mais fácil de manter quando comparado a versão com vários condicionais.

Conclusão

Como já discutido em boa parte dessa série: construir os códigos do projeto é necessário, porém refatorar para melhorá-los é uma outra opção a ser seguida.

A refatoração para o padrão State é um pouco mais tranquila do que algumas outras refatorações. A quantidade de código movido pode sim aparentar complexidade, mas na verdade é muito "copiar e colar" ao invés de "criar".

O padrão State não precisa de muita habilidade nossa, programadores, para ser descoberto quando utilizá-lo, apenas variáveis representando estados, com comparações acima de dois condicionais, esses indícios já são o suficiente para começar a complicar o código e se fazer necessária a refatoração para o State.

Algo que pode ser alterado no projeto com o intuito de permitir que a linguagem universal aplicada pelo padrão seja ainda mais forte (inglês é a linguagem mais comum em códigos): altere as partes dos nomes de classes de estado, mais precisamente altere o trecho Estado por State.

Outros artigos da série

Abaixo listo todos os artigos da série refatoração de código:

Internalizar Singleton

Mover Embelezamento Para Decorator

Introduzir Objeto Nulo

Unificar Interfaces Com Adapter

Extrair Adapter

Mover Conhecimento de Criação Para Factory

Substituir Notificações Hard-Coded Por Observer

Substituir Código de Tipo Por Classe

Extrair Parâmetro

Unificar Interfaces

Limitar Instanciação Com Singleton

Mover Acumulação Para Parâmetro Coletor

Compor Method

Formar Template Method

Substituir Lógica Condicional Por Strategy

Introduzir Criação Polimórfica com Factory Method

Encapsular Classes Com Factory

Encadear Construtores

Substituir Construtores Por Métodos de Criação

Fontes

Refatoração para Padrões

Vlw.

Receba em primeira mão, e com prioridade, os conteúdos Android exclusivos do Blog.
Email inválido

Relacionado

Padrão de Projeto: ObserverPadrão de Projeto: ObserverAndroid
Padrão de Projeto: AdapterPadrão de Projeto: AdapterAndroid
Padrão de Projeto: Objeto NuloPadrão de Projeto: Objeto NuloAndroid
Padrão de Projeto: State (Estado)Padrão de Projeto: State (Estado)Android

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...