Refatoração de Código: Formar Template Method

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: Formar Template Method

Refatoração de Código: Formar Template Method

Vinícius Thiengo20/04/2016
(606) (17) (6)
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 sobre métodos de Refatoração de Código. Dessa vez abordando o método de refatoração Formar Template Method que tem como objetivo diminuir a quantidade de códigos duplicados aplicando o padrão de projeto Template Method no algoritmo do software.

Ressalto que todos os métodos de refatoração dessa série podem ser utilizados em qualquer projeto de software que esteja com o paradigma orientado a objetos em uso.

Antes de prosseguir com os estudos do método proposto aqui é importante que você conheça o padrão de projeto Template Method. Caso ainda não o conheça, acesse o artigo aqui do Blog, indicado no link abaixo, que aborda somente esse padrão:

Padrão de Projeto: Template Method (Método Template)

Tópicos presentes no artigo:

Motivação

No projeto é identificado em uma hierarquia que algumas subclasses têm alguns métodos que executam passos similares e a execução desses passos está na mesma ordem.

Notada a presença de passos que variam e alguns que não variantes podemos trabalhar nesse código a aplicação do padrão Template Method. Assim será possível extrair métodos desses outros métodos que têm passos em comum, deixando com que os passos que não variam sejam implementados na superclasse aliviando ainda mais as subclasses e o projeto de código duplicado.

As subclasses serão responsáveis por implementar as partes que variam.

Código de exemplo

Para esse artigo vamos utilizar trechos de um código de uma entidade financeira, mais precisamente uma hierarquia de classes onde as classes são responsáveis por tipos de empréstimos bancários.

Vamos começar pela superclasse da hierarquia, CapitalStrategy:

public abstract class CapitalStrategy {
public abstract double capital( Emprestimo emp );
}

 

Então a subclasse CapitalStrategyLinhaSugerida:

public class CapitalStrategyLinhaSugerida extends CapitalStrategy {
public double capital( Emprestimo emp ){
return(
emp.getComprometimento() *
emp.getPorcentagemNaoUtilizada() *
duracao( emp ) *
fatorRisco( emp )
);
}
}

 

A outra subclasse, CapitalStrategyRecorrente:

public class CapitalStrategyRecorrente extends CapitalStrategy {
public double capital( Emprestimo emp ){
return(
(emp.proeminenteQuantidadeRisco() * duracao( emp ) * fatorRisco( emp ))
+
(emp.quantidadeRiscoNaoUtilizado() * duracao( emp ) * fatorRiscoNaoUtilizado( emp ))
);
}
}

 

E por fim a subclasse CapitalStrategyTempoLimitado:

public class CapitalStrategyTempoLimitado extends CapitalStrategy {
public double capital( Emprestimo emp ){
return( emp.getComprometimento() * duracao( emp ) * fatorRisco( emp ) );
}

public double duracao( Emprestimo emp ){
return( pesoDuracaoMedia( emp ) );
}

private double pesoDuracaoMedia( Emprestimo emp ){...}
}

 

Note que os métodos capital() em CapitalStrategyLinhaSugerida e CapitalStrategyTempoLimitado são bem similares, diferenciando-se somente devido a funcionalidade de "porcentagem não utilizada" sendo utilizada para o cálculo em CapitalStrategyLinhaSugerida.

Essa sequência similar de passos com uma leve variação já é suficiente para nos indicar uma possível implementação do padrão Template Method.

Note que também será possível otimizar a classe CapitalStrategyRecorrente, esse será nosso passo final na refatoração.

Mecânica

Utilizando o método de refatoração proposto aqui, temos como primeiro passo identificar o método que é similar em seu corpo, incluindo a ordem das execuções de scripts dentro dele. Isso nas subclasses de nossa hierarquia.

O método capital() é o nosso método similar, no caso em CapitalStrategyLinhaSugerida e CapitalStrategyTempoLimitado. O que faremos é aplicar a refatoração Compor Método.

Compor Método?

Isso mesmo. Com Compor Método estamos na verdade informando que vamos extrair métodos de nosso método similar, capital(). Mais precisamente extrairemos métodos idênticos e métodos únicos.

Métodos idênticos e métodos únicos?

Isso. Imagine que um dos passos dentro de nosso método similar era um cálculo do tipo: int x = y * 4 / 5. Agora assuma essa implementação sendo idêntica nesse método similar nas subclasses da hierarquia. O que devemos fazer é mover esse trecho para um método idêntico, ou seja, mesma assinatura e corpo.

O método único é a parte variante que estamos falando sobre desde o início do artigo. É o trecho de código que cada subclasse fornece, é um trecho de código único dela.

Ok, mas quando vou saber identificar se devo extrair métodos únicos ou métodos idênticos?

Bom, nesse caso o conhecimento do domínio do problema (Medicina, Futebol, Empréstimo, ...) pode lhe ajudar muito. Mas, se servir de apoio, qualquer caminho adotado tende a diminuir ainda mais os códigos repetidos e aliviar na quantidade de métodos abstratos da superclasse, conseguindo assim a implementação do padrão Template Method.

Dica:

Para saber ainda mais sobre o método de refatoração Compor Method, clique para ir ao artigo dele. Neste artigo terá também referência ao pré-requisito desse método, o padrão Cláusula de Guarda.

Prosseguindo com o código de exemplo, podemos extrair a chamada de método getPorcentagemNaoUtilizada() de CapitalStrategyLinhaSugerida como representando um método único dessa classe. Consequentemente teríamos uma implementação padrão para esse método único na superclasse CapitalStrategy.

Porém esse código de exemplo veio de um developer que tem anos de experiência como desenvolvedor de software em uma entidade financeira, ou seja, ele conhece bem o domínio do problema.

Logo, devido a experiência do programador do projeto, podemos com segurança escolher a opção de atualização onde vamos extrair não somente a chamada ao método getPorcentagemNaoUtilizada(), mas também a chamada ao método getComprometimento(). Essa alteração nos permitirá ter em código a representação do cálculo da “quantidade de risco de um empréstimo do tipo linha sugerida”:

Quantidade de Risco x Duração x Fator de Risco

Essa escolha também é a mais otimizada, não pelo desempenho, mas por passar para outros developers do projeto um código mais intuitivo quanto a solução que ele deve prover.

Observação:

Com a explicação anterior contendo também o conhecimento de um developer experiente na área, fica mais evidente, não somente para a aplicação do Template Method, a importância do conhecimento do domínio do problema do projeto.

Com isso temos agora um novo método, quantidadeRiscoPara(), em ambas as subclasses. Segue código em CapitalStrategyLinhaSugerida:

public class CapitalStrategyLinhaSugerida extends CapitalStrategy {
public double capital( Emprestimo emp ){
return(
quantidadeRiscoPara( emp ) *
duracao( emp ) *
fatorRisco( emp )
);
}

private double quantidadeRiscoPara( Emprestimo emp ){
return(
emp.getComprometimento() *
emp.getPorcentagemNaoUtilizada()
);
}
}

 

Então o código em CapitalStrategyTempoLimitado:

public class CapitalStrategyTempoLimitado extends CapitalStrategy {
public double capital( Emprestimo emp ){
return( quantidadeRiscoPara( emp ) * duracao( emp ) * fatorRisco( emp ) );
}

private double quantidadeRiscoPara( Emprestimo emp ){
return( emp.getComprometimento() );
}

public double duracao( Emprestimo emp ){
return( pesoDuracaoMedia( emp ) );
}

private double pesoDuracaoMedia( Emprestimo emp ){...}
}

 

O passo dois implica em mover os métodos idênticos para nossa superclasse. Bom, você já sabe sobre os métodos idênticos e sabe também que seguindo nossa refatoração não extraímos nenhum método desse tipo de nosso método similar, capital().

Note que toda vez que for optar por também ter métodos únicos (nosso caso) você deve se perguntar se para as subclasses da hierarquia faz sentido ter esse método como um método gancho (quando a superclasse fornece uma implementação comum) ou como um método contrato (quando a superclasse somente tem a assinatura abstrata dele).

Método contrato? Você não falou sobre ele no artigo do padrão Template Method.

Certo, você tem razão. Essa é apenas uma maneira de nos referenciarmos a métodos que estão como abstratos dentro de classes abstratas ou métodos em Interfaces. O contrato é para deixar claro que em classes concretas a implementação é necessária, não há escapatória.

Em nosso código faz todo sentido termos nosso método único quantidadeRiscoPara(), pois o polimorfismo está sendo implementando com classes de um mesmo domínio do problema (o caminho correto de implementação do polimorfismo por classes) e esse método será útil em todas as subclasses da hierarquia.

Resumo do passo dois nesse projeto de exemplo: não temos de fazer nada, pois o único método que extraímos é único. Caso em seu projeto você tenha métodos idênticos extraídos o que deverá fazer no passo dois é colocá-los todos na superclasse e removê-los das subclasses.

No passo três devemos nos assegurar que os métodos únicos nas subclasses têm todos as mesmas assinaturas. Em nosso caso o método quantidadeRiscoPara() já tem essa característica de mesma assinatura.

No passo quatro devemos também nos assegurar que os métodos similares (não é o idêntico, esse agora são nossos futuros métodos template) têm as mesma assinaturas, em nosso caso, novamente, já temos essa característica, capital() é nosso método similar e de mesma assinatura em ambas as subclasses incluindo a subclasse CapitalStrategyRecorrente da qual ainda não comentamos.

Note que esse "similar" é porque esses métodos têm dentro deles as chamadas aos métodos de implementação única em cada subclasse.

No passo cinco, sabendo que nosso método similar, capital(), tem a mesma assinatura e corpo nas subclasses, podemos movê-lo para a superclasse CapitalStrategy e junto também vamos mover o método quantidadeRiscoPara() para essa superclasse, porém com uma assinatura abstrata. Segue código atualizado de CapitalStrategy:

public abstract class CapitalStrategy {
public double capital( Emprestimo emp ){
return( quantidadeRiscoPara( emp ) * duracao( emp ) * fatorRisco( emp ) );
}

public abstract double quantidadeRiscoPara( Emprestimo emp );
}

 

Note que o tipo de acesso a quantidadeRiscoPara() também foi modificado, para public. Agora podemos chamar nosso método capital() de método template ou template method.

As subclasses ficam com as seguintes estruturas. Segue código de CapitalStrategyLinhaSugerida:

public class CapitalStrategyLinhaSugerida extends CapitalStrategy {
public double quantidadeRiscoPara( Emprestimo emp ){
return(
emp.getComprometimento() *
emp.getPorcentagemNaoUtilizada()
);
}
}

 

Segue código de CapitalStrategyTempoLimitado:

public class CapitalStrategyTempoLimitado extends CapitalStrategy {
public double quantidadeRiscoPara( Emprestimo emp ){
return( emp.getComprometimento() );
}

public double duracao( Emprestimo emp ){
return( pesoDuracaoMedia( emp ) );
}

private double pesoDuracaoMedia( Emprestimo emp ){...}
}

 

O método capital(), digo, a implementação dele, é parte somente da superclasse.

Para que a classe CapitalStrategyRecorrente também possa tirar proveito de nosso novo método template, vamos primeiro implementar nessa classe o método quantidadeRiscoPara(), onde dentro dele terá a chamada a proeminenteQuantidadeRisco():

public class CapitalStrategyRecorrente extends CapitalStrategy {
public double capital( Emprestimo emp ){
return(
(quantidadeRiscoPara( emp ) * duracao( emp ) * fatorRisco( emp )) +
(emp.quantidadeRiscoNaoUtilizado() * duracao( emp ) * fatorRiscoNaoUtilizado( emp ))
);
}

public double quantidadeRiscoPara( Emprestimo emp ){
return( emp.proeminenteQuantidadeRisco() );
}
}

 

Note que a primeira metade do cálculo em capital() da subclasse CapitalStrategyRecorrente é nada mais nada menos que uma implementação de nosso método template, capital(), logo temos agora o seguinte código atualizado:

public class CapitalStrategyRecorrente extends CapitalStrategy {
public double capital( Emprestimo emp ){
return(
super.capital( emp ) +
(emp.quantidadeRiscoNaoUtilizado() * duracao( emp ) * fatorRiscoNaoUtilizado( emp ))
);
}

public double quantidadeRiscoPara( Emprestimo emp ){
return( emp.proeminenteQuantidadeRisco() );
}
}

 

O método template, capital(), está sendo utilizado e ao mesmo tempo sendo sobrescrito. A segunda metade do cálculo é o cálculo de capital não utilizado. Podemos colocar nosso código ainda mais descritivo extraindo essa segunda metade em um método nomeado capitalNaoUtilizado():

public class CapitalStrategyRecorrente extends CapitalStrategy {
public double capital( Emprestimo emp ){
return(
super.capital( emp ) + unusedCapital( emp )
);
}

public double quantidadeRiscoPara( Emprestimo emp ){
return( emp.proeminenteQuantidadeRisco() );
}

public double unusedCapital( Emprestimo emp ){
return(
emp.quantidadeRiscoNaoUtilizado() *
duracao( emp ) *
fatorRiscoNaoUtilizado( emp )
);
}
}

 

Com isso terminamos a execução de nosso método de refatoração Formar Template Method e nosso código está mais limpo e eficiente para continuar evoluindo.

Conclusão

O método de refatoração Formar Template Method não é tão trivial de utilizar principalmente porque exige ainda mais o conhecimento do domínio do problema, porém ele permite a aplicarão do padrão Template Method em códigos já finalizados e que precisam de melhorias.

Com o Template Method os principais benefícios que conseguimos são: a remoção de código duplicado e a diminuição de métodos abstratos declarados na superclasse (não tão notável no exemplo do artigo), permitindo também um código que comunica mais facilmente o significado dos passos do algoritmo, código mais intuitivo.

Outros artigos da série

Abaixo listo todos os outros artigos já liberados dessa série de refatoração de código:

Internalizar Singleton

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

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 o conteúdo exclusivo do Blog, além de promoções de livros e cursos de programação.
Email inválido

Relacionado

Código Limpo - Habilidades Práticas do Agile SoftwareCódigo Limpo - Habilidades Práticas do Agile SoftwareLivros
O Codificador Limpo - Um código de conduto para programadores profissionaisO Codificador Limpo - Um código de conduto para programadores profissionaisLivros
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

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