Refatoração de Código: Formar Template Method
(2326) (2)
CategoriasAndroid, Design, Protótipo
AutorVinÃcius Thiengo
VÃdeo aulas186
Tempo15 horas
ExercÃciosSim
CertificadoSim
CategoriaDesenvolvimento Web
Autor(es)Robert C. Martin
EditoraAlta Books
Edição1ª
Ano2023
Páginas416
Tudo bem?
Neste artigo continuamos com a série sobre métodos de Refatoração de Código e código limpo.
Desta 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:
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 estaremos discutindo em 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 aplicaçã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.
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 listo todos os outros artigos já liberados desta série do Blog sobre "Codificação Limpa":
- 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.
Comentários Facebook