Refatoração de Código: Mover Embelezamento Para Decorator

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 /Refatoração de Código: Mover Embelezamento Para Decorator

Refatoração de Código: Mover Embelezamento Para Decorator

Vinícius Thiengo
(2063)
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 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:

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.

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.

A seguir os tópicos que abordaremos em artigo:

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.

Motivação

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.

Importante

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.

Código de exemplo

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:

Diagrama de 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:

public class StringNode extends AbstractNode {

private boolean shouldDecode = false;
...

public StringNode(
StringBuffer textBuffer,
int textBegin,
int textEnd,
boolean shouldDecode ){

this( textBuffer, textBegin, textEnd );
this.shouldDecode = shouldDecode;
}

private StringNode(
StringBuffer textBuffer,
int textBegin,
int textEnd ){
...
}

public String toPlainTextString(){
String result = textBuffer.toString();

if( shouldDecode ){
result = Translate.decode( result );
}

return result;
}

public void setDecoding( boolean shouldDecode ){
this.shouldDecode = shouldDecode;
}
...
}

 

E a classe cliente de StringNode, StringParser:

public class StringParser {
...

public StringNode find(
NodeReader reader,
String input,
int position,
boolean balanceQuotes ){

return new StringNode(
textBuffer,
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.

Mecânica

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:

Diagrama da classe AbstractNode

Temos um pequeno problema com ela, nada que a remova como candidata.

O problema é o estado da classe.

Estado da classe?

Sim.

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:

Diagrama da Interface Node

Uma melhor opção que AbstractNode.

Sem atributos, e quase todos os métodos públicos que StringNode utiliza.

Quase todos?

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.

São eles:

  • setText();
  • 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 interface Node {
...

public void setText( String text );
public String getText();
}

 

E então as implementações vazias desses dois métodos em AbstractNode:

public abstract class AbstractNode implements Node {
...

public void setText( String text ){}

public String getText(){
return null;
}
}

 

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!

Você tem razão.

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:

public class StringNode extends AbstractNode {
...

public StringNode(
StringBuffer textBuffer,
int textBegin,
int textEnd,
boolean shouldDecode ){

this( textBuffer, textBegin, textEnd );
setDecoding( shouldDecode ); /* CÓDIGO ATUALIZADO */
}
...

public String toPlainTextString(){
String result = textBuffer.toString();

if( shouldDecode() ){ /* CÓDIGO ATUALIZADO */
result = Translate.decode( result );
}

return result;
}

public void setDecoding( boolean shouldDecode ){
this.shouldDecode = shouldDecode;
}

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:

public class StringNode extends AbstractNode {
...

/* BLOQUEIO APLICADO VIA TIPO DE ACESSO private */
private StringNode(
StringBuffer textBuffer,
int textBegin,
int textEnd,
boolean shouldDecode ){

this( textBuffer, textBegin, textEnd );
setDecoding( shouldDecode );
}
...

/* NOVO MÉTODO DE CRIAÇÃO */
public static Node createStringNode(
StringBuffer textBuffer,
int textBegin,
int textEnd,
boolean shouldDecode ){

return new StringNode(
textBuffer,
textBegin,
textEnd,
shouldDecode );
}
...
}

 

Agora vamos atualizar o código cliente de StringNode, StringParser:

public class StringParser {

public Node find(
NodeReader reader,
String input,
int position,
boolean balanceQuotes ){

return new StringNode.createStringNode(
textBuffer,
textBegin,
textEnd,
reader.getParser().shouldDecodeNodes() );
}
}

 

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(
StringBuffer textBuffer,
int textBegin,
int textEnd ){

super( textBuffer, textBegin, textEnd );
}

protected boolean shouldDecode(){
return true;
}
}

 

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:

public class StringNode extends AbstractNode {
...

protected StringNode(
StringBuffer textBuffer,
int textBegin,
int textEnd ){
...
}
...

protected boolean shouldDecode(){
...
}
}

 

Agora voltamos ao método de criação de nossa classe componente concreto, StringNode, para atualizar a lógica para criar o componente correto:

...
public static Node createStringNode(
StringBuffer textBuffer,
int textBegin,
int textEnd,
boolean shouldDecode ){

if( shouldDecode ){
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:

public class StringNode extends AbstractNode {
... /* ESSES TRÊS PONTOS REPRESENTAM OUTROS PARÂMETROS E NÃO O shouldDecode QUE FOI REMOVIDO */

protected StringNode(
StringBuffer textBuffer,
int textBegin,
int textEnd ){
...
}

public static Node createStringNode(
StringBuffer textBuffer,
int textBegin,
int textEnd,
boolean shouldDecode ){

if( shouldDecode ){
return new DecodingNode(
textBuffer,
textBegin,
textEnd );
}

return new StringNode(
textBuffer,
textBegin,
textEnd );
}

public String toPlainTextString(){
String result = textBuffer.toString();

if( shouldDecode() ){
result = Translate.decode( result );
}

return result;
}

protected boolean shouldDecode(){
return false;
}
...
}

 

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():

...
public String toPlainTextString(){

String result = textBuffer.toString();

if( shouldDecode() ){ /* ESPERANDO UM true */
result = Translate.decode( result );
}

return result;
}
...

 

Então sobrescrevemos esse método em DecodingNode com o conteúdo correto:

public class DecodingNode extends StringNode {
...

public String toPlainTextString(){
return Translate.decode( textBuffer.toString() );
}
}

 

Agora removemos de StringNode essa lógica da versão true de shouldDecode():

public class StringNode extends AbstractNode {
...

public String toPlainTextString(){
return textBuffer.toString();
}
...
}

 

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:

public class DecodingNode extends StringNode {
...

public String toPlainTextString(){
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?

Sim.

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 {

private Node delegate;

public DecodingNode(
StringBuffer textBuffer,
int textBegin,
int textEnd ){

delegate = new StringNode( textBuffer, textBegin, textEnd );
}

public String toPlainTextString(){
return Translate.decode( super.toPlainTextString() );
}
}

 

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.

Então temos:

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.

Então temos:

public class DecodingNode implements Node {

private Node delegate;
...

public String toPlainTextString(){
/* 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 class DecodingNode implements Node {

private Node delegate;

public DecodingNode( Node node ){
delegate = 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 class DecodingNode implements Node {

private Node delegate;

public DecodingNode( Node node ){
delegate = node;
}

...

@Override
public CompositeTag getParent() {
return delegate.getParent();
}

@Override
public void setText( String text ) {
delegate.setText( text );
}

@Override
public String getText() {
return delegate.getText();
}
}

 

Agora atualizamos StringNode para a correta instanciação de DecondingNode:

public class StringNode extends AbstractNode {
...

public static Node createStringNode(
StringBuffer textBuffer,
int textBegin,
int textEnd,
boolean shouldDecode ){

if( shouldDecode ){
/* ABAIXO A INSTANCIAÇÃO ATUALIZADA */
return new DecodingNode( new StringNode( textBuffer, textBegin, textEnd ) );
}

return new StringNode( textBuffer, textBegin, textEnd );
}

...
}

 

Agora a classe DecodingNode é por completo uma classe decoradora.

Veja o novo diagrama:

Diagrama do novo trecho de código do projeto HTMLParser

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.

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

Curiosidade

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.

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, em primeira mão e também...

... na versão em PDF (versão liberada somente para os inscritos da lista de e-mails).

Abraço.

Outros artigos da série

Abaixo a lista de todos os artigos de refatoração de código e clean code já liberados aqui no Blog:

Fontes

Refatoração para Padrões

Use a Cabeça! Padrões de Projetos

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ões de Implementação - Um Catálogo de Padrões Indispensável Para o Dia a Dia do ProgramadorPadrões de Implementação - Um Catálogo de Padrões Indispensável Para o Dia a Dia do ProgramadorLivros
Refatoração Para PadrõesRefatoração Para PadrõesLivros
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

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