Template Method sempre foi um dos meus padrões favoritos, porém quando aplicado de forma exagerada acho que ele trouxe mais prejuízos do que benefícios.
A principal razão é que ele acaba dificultando o tratamento de erros da aplicação: ao padronizar o comportamento de alguma operação para uma hierarquia de classes, ele acaba também padronizando as exceções lançadas pelo método, e é aí que está o problema.
Um dos principais problemas que vejo em aplicações legadas é o tratamento de erros feito de forma inapropriada. Já vi algumas pessoas criticando, mas considero um excelente recurso da linguagem Java a existência de Checked Exceptions, utilizadas para erros que podem precisar de um tratamento particular.
Na implementação de uma biblioteca ou de um framework, checked exceptions talvez não sejam tão interessantes. Mas, na implementação de aplicações, considero um recurso bem valioso principalmente para aquelas divididas em camadas.
Template methods não casam bem com checked exceptions pelo seguinte motivo:
abstract protected void validar(Entidade entidade);
abstract protected void logarOperacao(Object algumaCoisa);
public void salvar (Entidade entidade) {
this.validar(entidade);
this.dao.persistir(entidade);
this.logarOperacao(algumaCoisa);
}
Um código como esse é comum em frameworks caseiros ou arquiteturas de referência. Geralmente fica em uma classe chamada MeuManagerGenerico, ou algo do tipo, e as diversas classes de negócio, específicas de cada entidade, devem implementar o método de validação e o método de log. O único problema com está abordagem é que você não está “amarrando” somente a assinatura e o algorítimo para salvar uma entidade, está também dizendo que nenhuma operação “salvar” do sistema lança uma checked exception.
A não ser que sua aplicação seja 100% CRUD, sem nenhuma restrição de unicidade em nenhum atributo, acho que essa é uma restrição muito forte e que não se aplica.
Dessa forma, se alguma regra de validação ou regra de negócio específica não for atendida, como você comunica para a camada superior (provavelmente a camada de visão) sobre o problema ocorrido? A única maneira é lançar unchecked exceptions.
Usando unchecked exception, como as camadas superiores saberão quais exceções lançadas pelas camadas inferiores? A única saída seria a camada inferior antecipar o tratamento dado ao erro, de maneira que a camada superior não precise trata-lo. Já vi muito essa abordagem com a exception tendo como atributo a chave da mensagem que será exibida ao usuário. Dessa forma a camada superior iria somente exibir a mensagem, não importando qual seja a exceção. Isso é um exemplo claro de quebra da divisão de responsabilidade entre as camadas. Se as camadas inferiores tem que antecipar de alguma forma a maneira como as camadas superiores fazem o tratamento de erro, porque você divide sua aplicação em camadas em primeiro lugar?
Esse um bom exemplo de que “amarração de código” e antecipação de necessidades podem trazer prejuízos não antecipados. Imagino que desenvolvedores que fazem template methods para operações com algoritmos simples como “salvar”, “atualizar”, “deletar”, etc só podem ter duas coisas em mente:
1. Economizar código.
Esse pensamento vem do falso sentimento de que uma aplicação enxuta é uma aplicação com pouco código. Temos que tomar cuidado pra não exagerar nas simplificações e não enxergar abstrações onde elas não existem ou são prejudiciais.
2. O desenvolvedor pode esquecer de fazer a validação ou gravar o log ou qualquer coisa do tipo, então “amarra-se” esse algoritmo em um template method.
Se seu desenvolvedor não sabe como fazer uma validação correta ou alguma dessas operações, ele não deveria colocar as mãos no código sem que estivesse fazendo programação em par com outro mais experiente.
Mesmo com essa armadilha, ainda acho válida a utilização do template method, mas somente nos raros casos onde se pode antecipar os erros de toda a hierarquia de classes e quando a complexidade do algoritmo justifique essa abordagem.