Retornando aos design patterns, desta vez falarei sobre o padrão Template Method. No entanto, neste post, utilizarei uma abordagem diferente: ao invés de definir o padrão e dar um exemplo, vou mostrar como refatorar código com o auxílio do padrão, melhorando o design de uma aplicação já existente.
O CENÁRIO
Imagine que na aplicação que esteja dando manutenção exista a classe abaixo, responsável por fazer o cálculo de uma prestação de um contrato:
public class Prestacao { public decimal Calcular(decimal valorTotal, int numeroDeParcelas) { var valorPrincipal = valorTotal / numeroDeParcelas; var valorDaTaxaAdministrativa = valorPrincipal * 0.01m; var valorDoSeguro = valorPrincipal * 0.015m; return valorPrincipal + valorDaTaxaAdministrativa + valorDoSeguro; } }
A classe é utilizada com sucesso na aplicação até que, em um belo dia, é necessário calcular o valor da prestação de outra forma, para alguns contratos específicos. Então, alguém cria a classe abaixo:
public class PrestacaoContratoEspecial { public decimal Calcular(decimal valorTotal, int numeroDeParcelas) { var valorPrincipal = valorTotal / numeroDeParcelas; var valorDaTaxaAdministrativa = (valorPrincipal * 0.01m) + 1.5m; var valorDoSeguro = 0.015m / valorPrincipal + 1; return valorPrincipal + valorDaTaxaAdministrativa + valorDoSeguro; } }
Pois bem. Percebam que ambas são bem parecidas, apresentando a mesma estrutura: 1) calcular o valor principal; 2) calcular a taxa administrativa; 3) calcular o seguro; 4) somar os 3 valores e retornar esta soma.
Imaginem agora que nos meses seguintes comecem a surgir novos contratos que exijam outros tipos de cálculos. O que faríamos? CTRL-C / CTRL-V e nova classe criada para o novo tipo de cálculo!
Assim, teríamos várias classes com comportamento semelhante esparramadas pela aplicação (sendo que as 2 exibidas acima ainda apresentam duplicação de código na primeira e na última linha).
PRIMEIRA REFATORAÇÃO
Esqueçam por um instante da simplicidade do exemplo acima e imaginem que cada passo do algoritmo (por ex: calcular o valor principal) fosse um pouco mais complexo. Poderíamos então refatorar o método Calcular, extraindo métodos para cada parte do algoritmo.
Vejamos como fica para a primeira classe “Prestacao”:
public class Prestacao { private decimal _valorPrincipal; private decimal _valorDaTaxaAdministrativa; private decimal _valorDoSeguro; public decimal Calcular(decimal valorTotal, int numeroDeParcelas) { CalcularValorPrincipal(valorTotal, numeroDeParcelas); CalcularValorDaTaxaAdministrativa(); CalcularValorDoSeguro(); return CalcularValorTotal(); } private void CalcularValorPrincipal(decimal valorTotal, int numeroDeParcelas) { _valorPrincipal = valorTotal / numeroDeParcelas; } private void CalcularValorDaTaxaAdministrativa() { _valorDaTaxaAdministrativa = _valorPrincipal * 0.01m; } private void CalcularValorDoSeguro() { _valorDoSeguro = _valorPrincipal * 0.015m; } private decimal CalcularValorTotal() { return _valorPrincipal + _valorDaTaxaAdministrativa + _valorDoSeguro; } }
Muito mais legível, não é? Lembrando que vocês veriam muito mais “vantagem” se cada passo fosse um pouco mais complexo.
Fazendo o mesmo para a classe PrestacaoContratoEspecial (exercício para vocês), teremos duas classes mais legíveis porém ainda com duplicação de código.
APLICANDO O PADRÃO
Com o padrão Template Method, podemos “refatorar para generalizar”, ou seja, podemos mover comportamento comum para uma classe base para evitar código duplicado.
Além disso, deixamos o design mais extensível, criando tipos novos de prestação sempre que precisarmos (ao invés de termos de alterar algumas classes, enchendo-as de “if’s”).
Vejamos o que diz a intenção do padrão, segundo o GoF (Gang of Four – Design Patterns):
“Definir o esqueleto de um algoritmo em uma operação, postergando alguns passos para as subclasses. Template Method permite que subclasses redefinam certos passos de um algoritmo sem mudar a estrutura do mesmo.”
Percebam novamente nos exemplos acima que temos uma sequencia de passos idêntica para o método Calcular de ambas as classes. Vamos então aplicar o padrão, criando uma classe abstrata para “abrigar” o método Calcular (que, de fato, é o nosso Template Method).
Chamaremos esta nova classe de “Prestacao” e a classe concreta “Prestacao” será renomeada para “PrestacaoContratoPadrao”:
public abstract class Prestacao { protected decimal _valorPrincipal; protected decimal _valorDaTaxaAdministrativa; protected decimal _valorDoSeguro; public decimal Calcular(decimal valorTotal, int numeroDeParcelas) { CalcularValorPrincipal(valorTotal, numeroDeParcelas); CalcularValorDaTaxaAdministrativa(); CalcularValorDoSeguro(); return CalcularValorTotal(); } protected virtual void CalcularValorPrincipal(decimal valorTotal, int numeroDeParcelas) { _valorPrincipal = valorTotal / numeroDeParcelas; } protected abstract void CalcularValorDaTaxaAdministrativa(); protected abstract void CalcularValorDoSeguro(); protected virtual decimal CalcularValorTotal() { return _valorPrincipal + _valorDaTaxaAdministrativa + _valorDoSeguro; } }
Vamos a algumas observações quanto à classe acima:
1) Uma vez que a intenção é variar os passos do algoritmo nas subclasses, não faz sentido sobrescrever o Template Method “Calcular”. Portanto, nada de “virtual” nele.
2) Todos os passos do algoritmo (demais métodos) são protegidos, já que não faz sentido uma classe-cliente usar somente um “pedaço” do algoritmo. Somente o Template Method “Calcular” é público.
3) Como “CalcularValorPrincipal” e “CalcularValorTotal” eram idênticos nos 2 tipos de prestação, levamos a implementação deles para a classe-base. No entanto, deixei um gancho (virtual) para que possam ser sobrescritos.
4) Os métodos “CalcularValorDaTaxaAdministrativa” e “CalcularValorDoSeguro” são operações primitivas (abstract), ou seja, DEVEM ser implementados pelas subclasses.
Finalizamos, então, alterando as subclasses concretas “PrestacaoContratoPadrao” (antiga “Prestacao”) e “PrestacaoContratoEspecial” para se adequar ao padrão:
public class PrestacaoContratoPadrao : Prestacao { protected override void CalcularValorDaTaxaAdministrativa() { _valorDaTaxaAdministrativa = _valorPrincipal * 0.01m; } protected override void CalcularValorDoSeguro() { _valorDoSeguro = _valorPrincipal * 0.015m; } } public class PrestacaoContratoEspecial : Prestacao { protected override void CalcularValorDaTaxaAdministrativa() { _valorDaTaxaAdministrativa = (_valorPrincipal * 0.01m) + 1.5m; } protected override void CalcularValorDoSeguro() { _valorDoSeguro = 0.015m / _valorPrincipal + 1; } }
Notem agora que ambas as classes herdam de “Prestacao” e implementam somente os métodos que variam. Legal, não é?
CONCLUSÃO
Neste post, refatoramos duas classes semelhantes (poderiam ser diversas) utilizando o design pattern Template Method.
O bom conhecimento de orientação a objetos e design patterns nos auxiliam na melhoria do design do software, ajudando a eliminar bad smells como a duplicação de código e a deixar o código mais limpo e extensível.
Lembrando que não é preciso “forçar a barra” para usar um design pattern. Conheça bem a intenção do padrão e avalie se realmente haverá ganho naquele momento.
Obs.: O código-fonte está em: https://github.com/robsoncastilho/RefactoringTemplateMethod (Fiz o exemplo no Visual Studio 2012 RC com .Net 4.5, mas você pode criar o projeto no 2010 e usar as classes do exemplo).
Parabéns pelo artigo, você conseguiu explicar de forma bem clara e objetiva.
CurtirCurtir
Obrigado pelo feedback.
[]s
CurtirCurtir
Excelente artigo para fixar melhor a idéia teórica que eu tinha sobre esse pattern, mas nunca utilizei.
CurtirCurtir
Obrigado, Wallison.
Agora é só praticar! (use com moderação)
CurtirCurtir
Com certeza entre os vários sites que pesquisei sobre padrões de projeto, o seu da de 10 a 0. Sempre muito bem explicado. Parabéns.
CurtirCurtir
Pô, cara.
Fico feliz com os elogios.
Pretendo escrever sobre outros padrões em breve!
Obrigado.
[]s
CurtirCurtir