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

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

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

Vinícius Thiengo
(2212) (2)
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 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":

Fonte

Refatoração para Padrões

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

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

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...
Israel (0) (0)
07/04/2020
Os outros artigos que li estavam bons, mas esse foi bem confuso...
Responder
Vinícius Thiengo (0) (0)
15/04/2020
Israel, tudo bem?

Vou dar uma revisada nele.

Confesso que alguns artigos antigos do Blog foram produzidos sem um conjunto necessário de revisão. Algo que ocorre com todos os conteúdos mais novos.

Vou verificar, assim que possível, e tirar ao menos as ambiguidades caso existam.

Surgindo dúvidas, pode perguntar.

Abraço.
Responder