Refatoração de Código: Internalizar Singleton

Receba em primeira mão o conteúdo exclusivo do Blog, além de promoções de livros e cursos de programação. Você receberá um email de confirmação. Somente depois de confirmar é que poderei lhe enviar o conteúdo exclusivo por email.

Email inválido.
Blog /Android /Refatoração de Código: Internalizar Singleton

Refatoração de Código: Internalizar Singleton

Vinícius Thiengo31/08/2016
(503) (5)
Go-ahead
"Não compare você mesmo com outros, pois é ai que começa a perder confiança em si próprio."
Will Smith
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áginas598
Comprar Livro
Conteúdo Exclusivo
Receba em primeira mão o conteúdo exclusivo do Blog, além de promoções de livros e cursos de programação.
Email inválido

Opa, blz?

Nesse artigo continuamos com a série Refatoração de Código para construção de códigos de melhor desempenho. Dessa vez vamos estar falando de um método simples e que parte de um padrão bem conhecido. O método Internalizar Singleton.

Em resumo, esse método de refatoração tem como objetivo remover Singletons desnecessários do projeto.

Mas por que removê-los? Se estão ali é porque foi uma escolha estratégica para o código, não?

Teoricamente sim, no decorrer do artigo vamos discutir mais sobre isso.

Note que todos os métodos de refatoração dessa série são úteis para qualquer software no paradigma orientado a objetos, independente da linguagem.

Antes de prosseguir é importante que você saiba o que é e como utilizar o padrão de projeto Singleton. Abaixo o link de um artigo aqui do Blog somente sobre esse padrão:

Tópicos presentes no artigo:

Motivação

Se você já está entendido com o padrão Singleton sabe que ele tem problemas, muito por ser um padrão que trabalha no contexto global.

Eu particularmente não enfrentei problemas com o Singleton até hoje, provavelmente é porque eu soube utilizá-lo. Mas se você ler literaturas de Engenharia de Software, principalmente de padrões de projeto e de métodos de refatoração, vai perceber que muitos autores não o indicam, nem o Singleton nem o padrão Registro, esse último que é muito similar ao Singleton, apesar de ser um padrão de Arquitetura de Aplicações Corporativas.

Agora você pergunta: por que todo esse desprezo na utilização do Singleton?

A princípio o problema está mesmo em criar uma estrutra, classe, de contexto global. Ou seja, os vários problemas que podem ocorrer devido ao uso de entidades globais no sistema.

Por exemplo: uma variável de um Singleton alterada em uma parte do projeto ocasionando um problema em outra que deveria ser independente.

Nesse caso o que resta ao developer é: onde está esse bug?

No pior dos casos: erro de lógica, nada de Exception, somente uma saída nada consistente com o que era esperado.

Uma outra possível explicação, e essa vem de Ward Cunningham, é a questão do ciclo de vida bem definido das entidades em projetos orientados a objetos.

Uma entidade global tem sim o ciclo de vida bem definido, enquanto o software estiver executando ou então aguardar o coletor de lixo liberar a memória que ela utilizava depois de assumir uma referência a null. Até ai tudo ok. Mas depois de a entidade já ter sido utilizada, um Singleton, ainda há referência a ela evitando que o Coletor de Lixo a remova da memória.

A, mas isso nem é um problema tão sério assim, ou é?

Para ambientes mobile, principalmente o Android, é um baita problema. Programador Android tem duas certezas na carreira: NullPointerException e OutOfMemmoryException. Esse último é frequente, pois a memória dos dispositivos que têm Android é muito limitada.

Adivinhe qual o problema que um objeto Singleton ajuda a gerar? O OutOfMemoryException, memory leak.

A literatura com possíveis problemas na utilização do Singleton é extensa, nomes como: Kent Beck, Martin Fowler, Ward Cunningham e Joshua Kerievsky têm explicações suficiente para fazer com que você tenha cautela ao utilizar o Singleton.

Existe até a menção da doença Singletoniti, onde o developer sai colocando Singleton em várias partes do código, onde, as vezes, somente passagem de uma referência ao objeto como parâmetro seria o bastante.

Com isso resumimos a motivação: nosso código utiliza alguns Singletons que não são necessários e podem até mesmo estar diminuindo o desempenho do processamento do software. Solução? Removê-los.

Código de exemplo

Nesse artigo vamos utilizar o código de um Blackjack para console. Isso mesmo, aquele jogo de cartas. Mas aqui vamos somente trabalhar com um trecho do projeto, o que tem um Singleton desnecessário.

Abaixo o Diagrama atual desse trecho de código:

Note que a classe Console é nossa classe Singleton.

Abaixo o diagrama que representa onde queremos chegar com essa refatoração:

Sem o Singleton, Console. Nessa refatoração vamos estar trabalhando diretamente com duas classes apenas, Blackjack e Console. Vamos também apresentar um código cliente para testes.

Abaixo os códigos de Console:

public class Console {
private static HitStayResponse hitStayResponse = new HitStayResponse();

private Console(){
super();
}

public static HitStayResponse obtainHitStayResponse( BufferedReader input ){
hitStayResponse.readFrom( input );
return hitStayResponse;
}

public static void setPlayerResponse( HitStayResponse newHitStayResponse ){
hitStayResponse = newHitStayResponse;
}
}

 

Note que para a instância de HitStayResponse ser retornada corretamente precisamos primeiro passar um BufferedReader como parâmetro. E também é possível com o código cliente passar uma nova instância de HitStayResponse, pelo método setPlayerResponse().

Agora os trechos que utilizaremos da classe Blackjack:

public class Blackjack {

public void play(){
...
do{
write("Hit or Stay: ");
hitStayResponse = Console.obtainHitStayResponse( input ); /* SINGLETON */
...
} while( canPlayerHit( hitStayResponse ) );
...
}
}

 

E então parte do código cliente para testes:

public class TestBlackjack {
public void testDealerStandsWhenPlayerBusts(){
/* INICIALIZANDO O SINGLETON COM UMA NOVA INSTÂNCIA */
Console.setPlayerResponse( new TestAlwaysHitResponse() );
int [] deck = {10, 9, 7, 2, 6};
Blackjack blackjack = new Blackjack( deck );

blackjack.play();

...
}
}

 

Note que no código anterior, mais precisamente na linha Console.setPlayerResponse( new TestAlwaysHitResponse() ); fica evidente que a instância de Blackjack precisa na verdade de um objeto do tipo TestAlwaysHitResponse quando  for necessário solicitar o objeto HitStayResponse do Singleton, Console.

Outra parte importante a se notar é que: da instanciação de TestAlwaysHitResponse até a utilização da instância no método play() de Blackjack não existe "n" camadas de divisão e sim apenas uma, a classe Blackjack.

Ok, mas o que você quer dizer com isso?

Que mesmo que somente uma instância de TestAlwaysHitResponse seja necessária na execução do game Blackjack, podemos passar a instância dessa classe diretamente como parâmetro em algum método ou construtor de Blackjack.

Por que?

Lembre dos problemas já citados em Motivação? Então, para evitá-los. Quando não precisamos de Singleton, não devemos utilizá-lo.

Mecânica

Nosso primeiro passo na refatoração Internalizar Singleton é encontrar uma classe "absorvedora" e então colocar nela os mesmos métodos públicos de nossa classe Singleton, Console.

Em nosso caso a classe absorvedora é a classe Blackjack, pois ela está utilizando diretamente a classe Singleton, Console.

Note que não há uma regra de escolha de classe absorvedora, pode ser qualquer uma outra, de preferência uma que já trabalhe com a classe Singleton.

Outro ponto importante: nos métodos públicos copiados para a classe absorvedora, Blackjack, as tarefas devem ser delegadas para o Singleton, em nosso caso, Console. Segue a nova configuração de Blackjack:

public class Blackjack {
...

public HitStayResponse obtainHitStayResponse( BufferedReader input ){
return Console.obtainHitStayResponse( input );
}

public void setPlayerResponse( HitStayResponse newHitStayResponse ){
Console.setPlayerResponse( newHitStayResponse );
}
}

 

Veja que como nossa classe absorvedora não é também um Singleton, nós podemos com segurança remover as palavras reservadas static dos métodos copiados em Blackjack.

Como assim "...nossa classe absorvedora não é também um Singleton"? Tem como ser um com essa refatoração?

Sim, pode parecer estranho, mas poderíamos sem problema algum remover um Singleton passando os estados (atributos) e comportamentos (métodos) públicos dele para outro Singleton, nesse caso o static deveria permanecer.

Agora no passo dois devemos alterar todas as chamadas ao Singleton, Console, para chamadas aos métodos da classe absorvedora, Blackjack. No código de testes agora temos:

public class TestBlackjack {
public void testDealerStandsWhenPlayerBusts(){
int [] deck = {10, 9, 7, 2, 6};
Blackjack blackjack = new Blackjack( deck );
/* NOVO CÓDIGO SUBSTITUINDO A CHAMADA ANTIGA VIA SINGLETON */
blackjack.setPlayerResponse( new TestAlwaysHitResponse() );

blackjack.play();

...
}
}

 

Então no código do método play() de Blackjack temos:

public class Blackjack {

public void play(){
...
do{
write("Hit or Stay: ");
/* NOVO CÓDIGO */
hitStayResponse = obtainHitStayResponse( input );
...
} while( canPlayerHit( hitStayResponse ) );
...
}

...
}

 

Podemos seguir ao passo três. Aqui devemos mover métodos e atributos da classe Singleton para a classe absorvedora, dessa forma não mais vamos depender da delegação ao Singleton.

Segue classe Blackjack atualizada:

public class Blackjack {
private HitStayResponse hitStayResponse = new HitStayResponse();
...

public HitStayResponse obtainHitStayResponse( BufferedReader input ){
hitStayResponse.readFrom( input );
return hitStayResponse;
}

public void setPlayerResponse( HitStayResponse newHitStayResponse ){
hitStayResponse = newHitStayResponse;
}
}

 

Note que como Blackjack não é também um Singleton, devemos seguramente remover a palavra reservada static do atributo movido para essa classe.

Mas já não tinhamos movido os métodos?

Não. Na verdade movemos somente a assinatura deles, onde o corpo deles era apenas a delegação para métodos correspondentes no Singleton. Agora movemos também o conteúdo e removemos a delegação.

Nosso quarto e último passo é o mais triste, remover o Singleton que teve as características movidas para a classe absorvedora. Brincadeiras a parte, remova o classe Singleton. Aqui, a classe Console.

Com isso finalizamos nossa refatoração, logo podemos relembrar o motivo da aplicação desse método: Internalizar Singleton.

Apesar de o jogo Blackjack precisar de somente uma instância de TestAlwaysHitResponse, essa instância é utilizada sem a necessidade de passar por várias camadas do projeto e também não há nada indicando que a instância deve ficar ativa até o fim do jogo.

Com isso e para evitar os possíveis problemas de um Singleton desnecessário, refatoramos o projeto para utilizar passagem de parâmetro ao invés de Singleton.

Se você leu o livro "Código Limpo - Habilidades Práticas do Agile Software" de Robert C. Martin provavelmente você deve ter lido as dicas informando sobre evitar parâmetros em métodos, quanto menos parâmetros, segundo o livro, melhor.

Preferir trabalhar com o padrão Injeção de Dependência, por exemplo, é uma boa escolha, se quiser seguir essa dica de Robert risca.

Eu, como você (caso tenha lido o livro), lembro desse trecho, mas é aquilo, você tem que saber "balancear". No caso do jogo Blackjack nós removemos um Singleton que era desnecessário, algo que o Robert provavelmente concordaria, até porque ele também é um seguidor de Kent Beck, que não curti o Singleton.

E mesmo que ele não concordasse, você e eu somos developers, devemos, muitas vezes, escolher o melhor caminho baseando-se em nossa experiência. Esse é o core do programador.

Você ainda deve ter a seguinte dúvida: o que você quer dizer com "várias camadas"?

Imagine que a classe Blackjack na verdade não precisasse diretamente da instância de TestAlwaysHitResponse, que essa instância fosse necessária em uma outra instância, dentro de uma instância que está dentro da instância de Blackjack (what mess!).

Ao menos três outras camadas seriam necessárias (cada objeto que receberia a instância de TestAlwaysHitResponse como parâmetro seria uma camada).

Nessa condição é preferível continuar com o Singleton, pois instâncias superiores nessa camada não têm de ter como parâmetros outras instâncias que elas não utilizam diretamente, alias essa prática nem mesmo é recomendada.

Conclusão

O Singleton, por ser fácil de utilizar e ter relação com vários padrões de projeto, muitas vezes é utilizado de forma demasiada.

Relação com outros padrões?

Sim, ele pode ser utilizado em conjunto com outros padrões, todas as versões do Factory, por exemplo.

Essa demasia no uso do Singleton pode trazer "n" problemas ao software, logo é inteligente ao menos estudar a possibilidade de remover alguns Singletons desnecessários, tendo em mente que muitos dados globais implica em memória sendo utilizada de forma ineficiente, algumas vezes. Isso sem citar os vários outros problemas das entidades globais.

Outros artigos da série

Segue lista de todos os artigos de refatoração de código presentes no Blog:

Mover Embelezamento Para Decorator

Substituir Condicionais que Alteram Estado por State

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

Use a Cabeça! Padrões de Projetos

Vlw.

Receba em primeira mão o conteúdo exclusivo do Blog, além de promoções de livros e cursos de programação.
Email inválido

Relacionado

As 33 Coisas que Todo Programador Deve Parar de FazerAs 33 Coisas que Todo Programador Deve Parar de FazerEmpreendedorismo
Use a Cabeça! Padrões de ProjetosUse a Cabeça! Padrões de ProjetosLivros
Padrão de Projeto: Decorator (Decorador)Padrão de Projeto: Decorator (Decorador)Android
Padrão de Projeto: SingletonPadrão de Projeto: SingletonAndroid

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