Refatoração de Código: Mover Embelezamento Para Decorator
Blog /Android /Refatoração de Código: Mover Embelezamento Para Decorator
Neste artigo continuamos com o conteúdo da série Refatoração de Código para documentar possíveis caminhos para obter maior performance dos projetos de software.
Desta vez vamos abordar o método de refatoração Mover Embelezamento Para Decorator.
Mover embelezamento? O que isso realmente significa?
"Mover embelezamento" em orientação a objetos é equivalente e remover de uma classe os códigos que são além da tarefa real dela (comumente em switchs ou if...else).
Então colocá-los em uma outra classe, nova ou já existente.
Ou seja, embelezamento pode ser comparado a código necessário no local errado.
Explicado "embelezamento" é preciso que antes de continuar o estudo do método de refatoração proposto aqui, que você saiba o que é e como utilizar o padrão de projeto Decorator.
Caso ainda não o conheça acesse o artigo aqui do blog somente sobre ele:
Padrão de Projeto: Decorator (Decorador).
A partir daqui vou assumir que você já está ciente do padrão acima.
Pegue um café, pois esse método de refatoração é um pouco mais detalhado (e complexo) que os outros já apresentados até o momento.
A seguir os tópicos que abordaremos em artigo:
Método de refatoração Substituir Código de Tipo por Subclasses;
Método de refatoração Substituir Herança por Delegação;
Método de refatoração Substituir Código de Tipo por Subclasses
E esse método, como alguns outros apresentados na série, é bem simples e dispensa um artigo somente para ele.
Essa refatoração indica que cada possível valor do tipo da variável que está sendo refatorada (em nosso caso a shouldDecode) deve se tornar uma subclasse da classe que essa variável se encontra (em nosso caso a classe StringNode) e que o método de leitura dessa variável deve ser sobrescrito pelas novas subclasses, retornando os valores que elas representam.
Método de refatoração Substituir Herança por Delegação
Método de refatoração simples que dispensa um artigo único para ele.
Remova a herança atual da classe e então crie (caso ainda não tenha) uma variável de instância do tipo da herança removida.
Nos métodos da subclasse, os que sobrescreviam os métodos da antiga herança, delegue a chamada para a chamada do mesmo método, porém da nova variável de instância.
Seu projeto já finalizado precisa de melhorias de códigos, a leitura dele não está tão boa e novas funcionalidades podem ser acrescentadas de acordo com o domínio do problema do software.
Um dos princípios de código limpo na orientação a objetos é deixar cada classe com apenas um objetivo (que pode envolver muitos métodos e atributos) e então dividir os outros objetivos, caso a classe atenda a mais de um, em outras classes.
Refatorando seu código você descobre que há algumas classes com esse problema, mais de um objetivo sendo atendido quando observando os métodos e atributos presentes nelas.
Uma possibilidade é trabalhar com o padrão de projeto Strategy.
Porém esse padrão requer que todas as classes que o utilize entidades de estratégia conheça-as.
Ou seja, o código não fica com a transparência necessária, além de ser preciso alterar algumas chamadas de métodos nas classes problemáticas (as que atendem a mais de uma meta).
Se pudermos evitar essa menor transparência, seria algo muito bom para um projeto que já está em produção.
Então temos uma outra solução, a implementação do padrão Decorator, dessa forma não precisaremos realizar grandes alterações no código cliente das classes problemáticas, pois o Decorator deixa o projeto transparente o suficiente para isso ocorrer.
As instâncias de classes envelopadas não sabem da existência das instâncias de classes decoradoras que são os invólucros delas.
Como com o padrão Strategy, a expansão do projeto prevalecerá a composição ao invés da herança, algo excelente, pois composição é mais flexível do que herança quando pensando na provável evolução do software.
O possível problema que podemos ter na implementação do padrão Decorator é com a classe base (o componente abstrato), mais precisamente em ela ter muitos métodos públicos que não serão alterados nas classes decoradoras.
Isso pode tirar um pouco do desempenho do algoritmo em termos de uso de memória, mesmo assim vale a implementação.
Lembrando que caso a queda no desempenho seja notável, aplica-se um medidor de desempenho no projeto para descobrir os verdadeiros gargalos.
Mesmo que ainda esteja um pouco confuso (provavelmente está) siga o código de exemplo e a refatoração, pois o entendimento será mais fácil.
Nesse artigo vamos utilizar como exemplo parte do código do projeto Java, HTMLParser.
Esse projeto tem como objetivo obter uma String HTML (com tags e textos) e então transformar todas as partes em objetos.
Por exemplo, objetos dos tipos: Tag, EndTag e StringNode.
O trecho que vamos estar estudando é exatamente o da classe StringNode e de algumas classes em volta dela (StringParser, AbstractNode e Node - essa última na verdade é uma Interface).
Antes de apresentar o código e o problema que a implementação de um Decorator vai solucionar, vamos ao diagrama atual desse trecho de código do projeto HTMLParser:
Algumas classes, métodos e atributos foram omitidos do diagrama anterior, isso para facilitar o entendimento.
O problema que temos de refatorar está no método setDecoding() de StringNode.
Essa classe já tem o objetivo de trabalhar com o texto do HTML, a parte de decodificar textos ainda em HTML code (exemplo: ç em HTML code é: ç) entra como uma funcionalidade extra que pode ser convertida em uma classe decoradora, até porque a classe irmã de StringNode, RemarkNode, também poderá posteriormente utilizar essa funcionalidade.
Pois RemarkNode é responsável por textos em comentários HTML, que também podem ter esses códigos.
Por que não somente acrescentamos esse método com uma definição padrão a AbstractNode?
É uma opção, porém a classe Tag não precisará nunca desse método.
Ou seja, estaremos de certa forma inflando a memória para cada nova instância de Tag.
O recomendado para classes base é a implementação somente de métodos e atributos herdados por todas as subclasses diretas.
Com isso podemos apresentar o código de início do exemplo, vamos apresentar os trechos necessários para a explanação no artigo.
Segue classe StringNode:
private boolean shouldDecode = false;
public StringNode(
StringBuffer textBuffer,
int textBegin,
int textEnd,
boolean shouldDecode ){
this( textBuffer, textBegin, textEnd );
this.shouldDecode = shouldDecode;
private StringNode(
int textEnd ){
public String toPlainTextString(){
String result = textBuffer.toString();
if( shouldDecode ){
result = Translate.decode( result );
public void setDecoding( boolean shouldDecode ){
E a classe cliente de StringNode, StringParser:
public StringNode find(
NodeReader reader,
boolean balanceQuotes ){
return new StringNode(
textBegin,
textEnd,
reader.getParser().shouldDecodeNodes() );
Os códigos da superclasse AbstractNode e da Interface Node vamos omitir aqui nessa seção. Pois não será necessário alterar nenhum dos métodos dessas, somente adicionar com o decorrer da refatoração.
As classes Tag e RemarkNode estão no diagrama somente para apresentar que uma, RemarkNode, terá acesso as mesmas classes decoradas da classe irmã, StringNode. Já a outra, Tag, não.
O diagrama servindo, então, como explicação do porquê não trabalhar com o método setDecoding() em AbstractNode.
Com isso podemos seguir para a refatoração.
Nosso primeiro passo é procurar a classe ou Interface que represente o componente abstrato (essa classe pode ser concreta também, não há problemas quanto a isso).
Sabendo que nosso componente concreto alvo é StringNode, temos que a classe abstrata, AbstractNode, é uma candidata a classe componente abstrato.
Candidata apenas?
Sim, pois temos de verificar se ela tem todos os métodos públicos que StringNode tem, caso não, temos ainda a Interface Node.
A seguir é apresentado o diagrama completo somente da classe AbstractNode:
Temos um pequeno problema com ela, nada que a remova como candidata.
O problema é o estado da classe.
Estado da classe?
Os atributos dela (atributos de classe são o equivalente ao estado dela).
As classes decoradoras não precisam desses atributos, até porque eles já são parte da classe que será decorada, StringNode.
Lembrando que as classes decoradoras e as classes decoradas devem ser do mesmo tipo.
Agora vamos ao diagrama completo da Interface Node:
Uma melhor opção que AbstractNode.
Sem atributos, e quase todos os métodos públicos que StringNode utiliza.
Sim, não tinha apresentado os códigos de StringNode por completo.
Mas essa classe tem outros dois métodos que não são descritos em Node e nem em AbstractNode.
e getText().
O que fazer se Node é uma Interface?
Colocar as assinaturas desses métodos nele implica na implementação obrigatória por parte de todas as classes concretas abaixo de Node, na hierarquia.
Uma possível solução é colocar as assinaturas dos métodos em Node e em AbstractNode colocar as definições como vazias, sem serem abstract. Vamos adotar essa estratégia.
Segue métodos em Node:
public void setText( String text );
E então as implementações vazias desses dois métodos em AbstractNode:
public abstract class AbstractNode implements Node {
public void setText( String text ){}
Esse trecho de código é necessário para que as classes decoradoras tenham exatamente a mesma interface pública que StringNode, nossa classe componente concreto.
Você provavelmente deve estar pensando:
Mas o problema de não termos colocado o setDecoding() em AbstractNode foi justamente não forçar a herança de métodos não utilizados em subclasses dessa. Agora você faz exatamente isso!
Uma dica muito importante antes de implementar o padrão Decorator:
Estude as classes que serão refatoradas para saber se realmente vale a implementação desse padrão.
Acredite, as classes aqui, de HTMLParser, foram sim estudadas. Mesmo assim não é possível prever tudo.
De qualquer forma os métodos getText() e setText() são ao menos compartilhados por algumas das classes irmãs de StringNode, algo que não é verdade para setDecoding().
Um outro questionamento que você pode ter é:
E se não houvesse nenhuma herança na classe componente concreto, StringNode, ou se houvesse uma que não implementasse nem mesmo a metade dos métodos públicos em StringNode? O que faríamos nesse caso?
Nesse contexto deveríamos criar nosso componente abstrato.
Ou uma Interface com a declaração de todos os métodos públicos de StringNode e consequentemente fazendo com que StringNode também implementasse essa Interface ou até mesmo utilizando uma classe abstrata ao invés de uma Interface.
No caso, StringNode deveria estender essa classe abstrata.
Com isso podemos seguir para o passo dois.
Nesse passo começamos a definir a classe decoradora. Porém ela ainda será apenas uma subclasse de nossa classe componente concreto, StringNode.
Devemos encontrar o código extra que deverá se tornar parte da classe decoradora.
Em nosso caso é todo código em StringNode que trabalha com a variável de instância shouldDecode.
Essa variável é responsável pelos códigos de embelezamento criados em StringNode e pelo método setDecoding().
Ok, e se houver mais de uma classe embelezada ou até mesmo códigos completamente diferentes, em termos de objetivo, dentro de minha classe componente concreto. Nesse caso continuo a refatoração para gerar somente uma subclasse?
Na verdade esse passo dois será aplicado para todos os códigos de embelezamento em suas subclasses componente concreto, um código de cada vez.
Em nosso projeto somente temos um código com objetivo extra, o referente a shouldDecode.
E somente uma única classe componente concreto com código extra, StringNode.
Se houvessem mais códigos extras, logo depois de aplicar o passo dois para códigos de shouldDecode aplicaríamos para o próximo código extra.
Depois de encontrado os trechos de códigos em StringNode que trabalham diretamente com essa variável de instância, devemos substituir essas interações diretas por interações encapsuladas.
Interações encapsuladas?
Sim, caso não exista, vamos criar métodos get e set para essa variável, dessa forma todo o código da classe componente concreto poderá utilizar os métodos (interações encapsuladas) ao invés da variável diretamente.
O método set para shouldDecode já temos em StringNode, é o setDecoding().
Vamos somente acrescentar o método shouldDecode() e atualizar alguns trechos em StringNode que referenciam shouldDecode:
setDecoding( shouldDecode ); /* CÓDIGO ATUALIZADO */
if( shouldDecode() ){ /* CÓDIGO ATUALIZADO */
private boolean shouldDecode(){ /* CÓDIGO ACRESCENTADO */
return shouldDecode;
Mas não deveríamos criar uma nova classe para futuramente ela se tornar uma classe decoradora ao invés de apenas encapsular a interação de uma variável de instância?
Sim, mas esse passo de encapsular a interação é parte da refatoração onde criaremos uma nova classe.
E não terminamos ainda o encapsulamento, pois um dos construtores de StringNode tem um shouldDecode entrando como parâmetro.
Vamos criar um método de criação para StringNode, isso vai permitir que o código, posteriormente, crie a instância correta, uma classe componente concreto ou uma classe decoradora.
Isso sem precisar que o código cliente informe diretamente a classe que deseja instanciar.
Não confunda essa refatoração de método de criação com o método de refatoração Substituir Construtores Por Métodos de Criação, pois no caso desse projeto a motivação é diferente.
Segue classe StringNode com o novo método de criação e o bloqueio do construtor para instanciação:
/* BLOQUEIO APLICADO VIA TIPO DE ACESSO private */
setDecoding( shouldDecode );
/* NOVO MÉTODO DE CRIAÇÃO */
public static Node createStringNode(
shouldDecode );
Agora vamos atualizar o código cliente de StringNode, StringParser:
public Node find(
return new StringNode.createStringNode(
Note que alteramos também o retorno do método, antes era StringNode, mudamos para Node.
Podemos aplicar essa alteração com segurança.
Pois já sabemos qual é nossa entidade componente abstrato e é esse tipo de entidade que será retornada pelo método de criação, mesmo que ainda esteja sendo utilizado o tipo específico, StringNode.
Nossa próxima tarefa ainda no passo dois é criar uma subclasse de nossa classe componente concreto, StringNode, para cada possível valor de shouldDecode.
Porém sabemos que a decoração que estamos criando é para permitir que códigos HTML nos nodos de texto possam ser decodificados.
Antes dessa funcionalidade o que temos é apenas a classe StringNode com os métodos úteis a tarefa principal dela.
Ok, mas o que você quer dizer com isso?
Simples. shouldDecode é do tipo boolean, logo permite somente os valores true ou false.
Porém o false já é a implementação comum (sem código de embelezamento) de StringNode, logo devemos criar somente uma classe extra, a que corresponde ao valor true, que realiza a decodificação.
Essa classe vai se chamar DecodingNode.
Nesse início de classe devemos sobrescrever somente o método de leitura de shouldDecode, lembrando que essa é a variável responsável pela criação dessa nova classe:
public class DecodingNode extends StringNode {
public DecodingNode(
super( textBuffer, textBegin, textEnd );
protected boolean shouldDecode(){
Note que devido a essa nova classe devemos atualizar o modificador de acesso do método shouldDecode() e de um dos construtores de StringNode, ambos para protected:
protected StringNode(
Agora voltamos ao método de criação de nossa classe componente concreto, StringNode, para atualizar a lógica para criar o componente correto:
return new DecodingNode( textBuffer, textBegin, textEnd );
return new StringNode( textBuffer, textBegin, textEnd );
Note que o construtor de StringNode utilizado também foi alterado, sem o argumento shouldDecode, a partir desse ponto.
Alias podemos remover quase todo o código de shouldDecode de StringNode, veja como essa classe fica depois de atualizada:
... /* ESSES TRÊS PONTOS REPRESENTAM OUTROS PARÂMETROS E NÃO O shouldDecode QUE FOI REMOVIDO */
return new DecodingNode(
textEnd );
if( shouldDecode() ){
Por que shouldDecode() permanece e por que com o retorno false?
Bom, antes vou ser sincero contigo.
Essa refatoração de extrair uma classe, mais precisamente aplicando esses passos todos somente para extrair uma futura classe decoradora, na verdade esses passos representam uma refatoração completa chamada Substituir Código de Tipo por Subclasses.
Em nosso caso já informamos que a classe StringNode representa o valor false que era possível em shouldDecode, logo, apesar de não precisarmos de uma nova classe que represente false, devemos ainda ter o método de leitura de shouldDecode retornando o valor que essa classe representa, false.
Com isso conseguimos já explicar o porquê do retorno true em shouldDecode() de DecodingNode e o porquê dessa classe está herdando de StringNode.
Nossa próxima tarefa, ainda no passo dois, é mover todo código de lógica condicional, em nossa classe componente concreto que espera um true de shouldDecode(), para a classe DecodingNode.
Lembrando que esse método em StringNode nunca retorna true.
Em StringNode temos esse tipo de lógica em toPlainTextString():
if( shouldDecode() ){ /* ESPERANDO UM true */
Então sobrescrevemos esse método em DecodingNode com o conteúdo correto:
return Translate.decode( textBuffer.toString() );
Agora removemos de StringNode essa lógica da versão true de shouldDecode():
Se notar bem, no código de toPlainTextString() de DecodingNode temos um código duplicado quando comparado a StringNode, a superclasse.
Nesse caso devemos fazer a seguinte atualização:
return Translate.decode( super.toPlainTextString() );
Até esse ponto aplicamos um outro método de refatoração, Substituir Condicional por Polimorfismo.
Método de refatoração Substituir Condicional por Polimorfismo
Nesse método de refatoração, a subclasse recebe, com a mesma assinatura de método da superclasse, um novo método que terá como conteúdo o trecho da lógica de negócio que estava no código do mesmo método.
Porém na versão da superclasse.
O trecho de código que deve ser movido é o que é vinculado a tarefa que subclasse é responsável.
Caso a subclasse não exista, crie ela e mova o trecho de código.
O condicional era o do método toPlainTextString() da classe StringNode.
Agora com a atualização no mesmo método, porém o de DecondingNode, estamos trabalhando com polimorfismo.
Notou algo no código de StringNode?
Sem problemas se não notou. Mas o método shouldDecode() não é mais utilizado, logo podemos removê-lo de StringNode e de DecondingNode.
Mas manter esse método não é um passo da refatoração Substituir Código de Tipo por Subclasses?
Mas note que as refatorações utilizadas na refatoração Mover Embelezamento para Decorator são apenas para auxílio a ela, nosso objetivo é o Decorator, logo, se forem necessárias algumas atualizações a mais no código, iremos aplicá-las.
Se houverem mais códigos de embelezamento em suas classes componente concreto, repita o passo dois para cada um deles.
Assim podemos partir para o passo três. Em nosso caso somente uma subclasse foi gerada.
No passo três devemos aplicar a refatoração Substituir Herança por Delegação a todas as subclasses de nossa classe componente concreto.
Então somente precisamos garantir que a variável de instância que será criada é do tipo do componente abstrato, em nosso caso, a Interface Node, e remover a herança atual.
Atualizando a subclasse DecodingNode temos:
public class DecodingNode {
delegate = new StringNode( textBuffer, textBegin, textEnd );
Removemos a herança e colocamos a nova variável de instância, que terá como valor atribuído um tipo de classe decoradora ou classe componente concreto.
Espere! Classe decoradora não.
Devemos voltar com a herança, mais precisamente com a implementação de nossa Interface que representa a entidade componente abstrato, Node.
public class DecodingNode implements Node {
Nesse caso, todos os métodos de Node devem ser implementados por DecodingNode.
Mas antes note que os métodos herdados de nossa antiga herança (StringNode) e que tinham chamadas diretas a versão da superclasse, devem agora utilizar a delegação para essas chamadas.
/* UTILIZANDO A VARIÁVEL delegate AO INVÉS DE super */
return Translate.decode( delegate.toPlainTextString() );
Agora no quarto e último passo devemos passar o objeto da classe componente concreto, StringNode, como parâmetro pelo construtor da classe decoradora, DecodingNode, e então atribuí-lo a variável delegate:
public DecodingNode( Node node ){
Antes de atualizar o código de StringNode devemos delegar todas as chamadas aos métodos.
Agora implementados em DecodingNode, para a variável delegate:
public CompositeTag getParent() {
delegate.setText( text );
return delegate.getText();
Agora atualizamos StringNode para a correta instanciação de DecondingNode:
/* ABAIXO A INSTANCIAÇÃO ATUALIZADA */
return new DecodingNode( new StringNode( textBuffer, textBegin, textEnd ) );
Agora a classe DecodingNode é por completo uma classe decoradora.
Veja o novo diagrama:
Provavelmente você deve estar se perguntando:
Onde está a classe decoradora abstrata?
Como precisamos de apenas uma única classe decoradora não foi necessária essa classe que muito faz em evitar repetição de código, algo que não temos, por ser exatamente uma única classe decoradora.
Bom, com isso terminamos a refatoração proposta no artigo.
Note que essa é uma das refatorações mais difíceis, pois envolve muitas outras refatorações menores.
Apesar disso é um caminho válido para se colocar o Decorator no projeto caso seja identificada a oportunidade de melhorá-lo com a aplicação desse padrão.
Antes de aplicar o padrão Decorator estude as classes envolvidas na possível refatoração e defina se realmente esse é o melhor padrão (você vai enxergar se um Decorator vai causar menos atualização no cliente do que o Strategy).
Caso sim, aplique o método de refatoração.
Fique ciente que os passos que geraram somente a classe DecodingNode vão gerar mais classes se em seu caso houver mais de um código de embelezamento nas classes componente concreto.
Em caso de mais classes decoradoras sendo construídas, aplique a criação da classe decoradora abstrata logo depois ou antes do passo final apresentado aqui.
Esse passo da classe decoradora abstrata será tranquilo, lembrando que ela muito faz em evitar repetição de código.
Se você chegar até essa refatoração, muito provavelmente seu código já está com uma performance excelente, pois a refatoração Mover Embelezamento para Decorator é "pesada" e requer que o developer do código realmente se comprometa com a melhora dele.
Abaixo a lista de todos os artigos de refatoração de código e clean code já liberados aqui no Blog:
Padrão de Projeto: Decorator (Decorador)Android
código limpo - engenharia de software - clean code - padrões de projeto - refatoração - alta performance - programador eficiente - decorator - mover embelezamento