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

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 /Padrão de Projeto: Template Method (Método Template)

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

Vinícius Thiengo01/09/2016, Quinta-feira, às 20h
(734) (2) (11) (12)
Go-ahead
"Na falta de um foco externo, a mente se volta para dentro de si mesma e cria problemas para resolver, mesmo que os problemas são indefinidos ou sem importância. Se você encontrar um foco, uma meta ambiciosa que parece impossível e força-o a crescer, essas dúvidas desaparecem."
Tim Ferriss
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 vamos apresentar um padrão eficiente para você otimizar seu projeto de software, digo, reduzir a quantidade de códigos duplicados.

O padrão de projeto abordado será o Template Method, que é um pouco similar ao padrão Factory Method, alias, esse último é uma especialização do Template Method.

Tópicos presentes no artigo:

Apresentação

O padrão apresentado aqui é pré-requisito para o entendimento do método de refatoração Formar Template Method.

Vamos iniciar com a definição do padrão proposto:

O Template Method defini o esqueleto (passos) do algoritmo em uma operação, permitindo que subclasses implementem alguns passos específicos do processamento.

Confuso? Bom, vamos continuar que o entendimento será tranquilo no decorrer do conteúdo. Sério, esse padrão é tranquilo de entender.

Vamos começar com o diagrama e logo depois o código de exemplo.

Diagrama 

Não está explícito no diagrama, mas o método metodoTemplate() é final, não poderá ser sobrescrito por subclasses de ClasseTemplateAbstrata.

Todos os métodos: operacaoConcreta_1(), operacaoConcreta_2() e operacaoAbstrata_1(). Todos esses são utilizados dentro de metodoTemplate() em uma ordem bem definida e compartilhada por subclasses de ClasseTemplateAbstrata.

Uma variação do diagrama acima que você pode encontrar em outras literaturas de engenharia de software é a seguinte:

metodoGancho_1()?

Sim. Em Template Method métodos gancho ficam na classe de mais alto nível da hierarquia, ClasseTemplateAbstrata. Eles têm, ou uma implementação vazia (sem linhas de código entre as chaves de abertura e fechamento de bloco) ou uma implementação padrão.

Nesse caso é comum que os métodos: operacaoConcreta_1() e operacaoConcreta_2(). Que esses métodos sejam também final como o método metodoTemplate(), ou seja, não podem ser sobrescritos.

O método metodoGancho_1() pode ser sobrescrito, mas não é obrigatório. Já o método operacaoAbstrata_1() têm que ser sobrescrito, pois é abstrato.

Até aqui provavelmente nada ficou muito claro ainda. Vamos então seguir para o código de exemplo. Como falei no início do artigo: esse é um padrão fácil de entender.

Código de exemplo

Vamos trabalhar com um pequeno projeto de cafeteria, mais precisamente com as classes responsáveis pela criação de café e chá, ambas bebidas cafeinadas (indício de uma superclasse).

Note que ambos os códigos de classe têm métodos muito similares. Vamos começar com o código da classe Cafe:

public class Cafe {
public void preparar(){
esquentarAgua();
moerCafe();
colocarEmCopo();
addAcucarELeite();
}

public void esquentarAgua(){
System.out.println("Esquentando água");
}

public void moerCafe(){
System.out.println("Moendo café");
}

public void colocarEmCopo(){
System.out.println("Colocando no copo");
}

public void addAcucarELeite(){
System.out.println("Adicionando açúcar e leite");
}
}

 

Note principalmente os passos (algoritmo) de processamento no método preparar().

Agora a classe Cha:

public class Cha {
public void preparar(){
esquentarAgua();
prepararSaquinhoDeCha();
colocarEmCopo();
addLimao();
}

public void esquentarAgua(){
System.out.println("Esquentando água");
}

public void prepararSaquinhoDeCha(){
System.out.println("Preparando o saquinho de chá");
}

public void colocarEmCopo(){
System.out.println("Colocando no copo");
}

public void addLimao(){
System.out.println("Adicionando algumas gotas de limão");
}
}

 

Notou algo igual?

Isso mesmo. Apesar de os métodos nem sempre terem os mesmos nomes em ambas as classes eles são similares em execução (moer café para um café é similar a preparar o saquinho de chá para um chá). Isso sem comentar os passos que são exatamente os mesmos.

Como informado, o método preparar() é exatamente o mesmo, digo, mudando alguns nomes de métodos, ele tem exatamente os mesmos passos necessários para ambas as classes.

Seria ele nosso método template?

Provavelmente, vamos prosseguir. Veja como é evidente o problema de repetirão de código em ambas as classes.

Note que muitos dos padrões de projeto que existem hoje em dia têm, em sua maioria, um único objetivo: reduzir ou acabar de vez (anhan, sei!) com o códigos duplicados.

Código duplicado é equivalente a:

  • Manutenção mais lenta e crítica. Corrigindo ou acrescentando algo você terá de lembrar de atualizar também o trecho de código igual em outras partes do projeto;
  • Maior consumo de memória no processamento. Mais códigos (mesmo que repetidos), mais espaços e quantidade de recursos como necessidade de execução;
  • Aumento dos custos monetários de evolução do projeto, pois a leitura e atualização do código ficam mais lentos.

Provavelmente tem "n" outros problemas, mas os indicados acima já nos ajudam a entender o porquê de tantos padrões para remover códigos repetidos.

Com isso podemos seguir a solução: aplicar o padrão Template Method.

Primeiro informo que nosso objetivo aqui não é aplicar passo a passo uma refatoração, isso você consegue acessando o método de refatoração que parte desse padrão: Formar Template Method.

Aqui vamos somente apresentar as modificações no projeto de exemplo.

Vamos começar alterando o que é necessário para podermos criar um método template.

Já sabemos que as classes são relacionadas, ambas são classes de bebidas cafeinadas. Logo podemos aproveitar esse ponto e estudar melhor os nomes dos métodos com execuções equivalentes, porém com rótulos distintos.

O que? O que está falando?

Lembra dos métodos moerCafe() e addAcucarELeite() da classe Cafe e dos métodos prepararSaquinhoDeCha() e addLimao() da classe Cha?

Então, esses métodos devem ser compatíveis em suas assinaturas para que a criação de um método template seja possível.

Vamos renomear os métodos moerCafe() e prepararSaquinhoDeCha() para mistura(). Para os métodos addAcucarELeite() e prepararSaquinhoDeCha() temos um nome mais comum a ação deles: addCondimentos().

Com isso segue as novas configurações de classe. Primeiro a classe Cafe:

public class Cafe {
public void preparar(){
esquentarAgua();
mistura();
colocarEmCopo();
addCondimentos();
}
...

public void mistura(){
System.out.println("Moendo café");
}
...

public void addCondimentos(){
System.out.println("Adicionando açúcar e leite");
}
}

 

Então Cha:

public class Cha {
public void preparar(){
esquentarAgua();
mistura();
colocarEmCopo();
addCondimentos();
}
...

public void mistura(){
System.out.println("Preparando o saquinho de chá");
}
...

public void addCondimentos(){
System.out.println("Adicionando algumas gotas de limão");
}
}

 

Ok, com isso podemos prosseguir criando uma superclasse para as bebidas.

Antes, note a importância dos passos executados em preparar(). A ordem é fundamental e igual em ambas as classes. Esse será nosso método template ou, como também conhecido em algumas literaturas de engenharia de software: método gabarito.

Podemos assim criar a superclasse BebidaCafeinada:

public abstract class BebidaCafeinada {
public final void preparar(){ /* O TEMPLATE METHOD */
esquentarAgua();
mistura();
colocarEmCopo();
addCondimentos();
}

public void esquentarAgua(){
System.out.println("Esquentando água");
}

public abstract void mistura();

public void colocarEmCopo(){
System.out.println("Colocando no copo");
}

public abstract void addCondimentos();
}

 

Como informado no diagrama do padrão, nosso método template é final, não pode ser sobrescrito. Quanto aos métodos concretos, se tiver o conhecimento de que eles sempre deverão apresentar processamentos iguais, os coloque também como final.

Como em mistura() e em addCondimentos() temos processamentos diferentes nas subclasses, os colocamos como abstract.

Assim podemos ir as novas configurações das classe Cafe e Cha, começando com Cafe:

public class Cafe extends BebidaCafeinada {
@Override
public void mistura(){
System.out.println("Moendo café");
}

@Override
public void addCondimentos(){
System.out.println("Adicionando açúcar e leite");
}
}

 

E então Cha:

public class Cha extends BebidaCafeinada {
@Override
public void mistura(){
System.out.println("Preparando o saquinho de chá");
}

@Override
public void addCondimentos(){
System.out.println("Adicionando algumas gotas de limão");
}
}

 

Assim removemos códigos duplicados, conseguindo a atualização, quando necessária, em apenas um ponto. Vamos a um código cliente de exemplo:

public class ClienteCafeteria {
public static void main( String args[] ){
Cafe cafe = new Cafe();
Cha cha = new Cha();

cafe.preparar();
/*
Esquentando água
Moendo café
Colocando colocando no copo
Adicionando açúcar e leite
*/

cha.preparar();
/*
Esquentando água
Preparando o saquinho de chá
Colocando colocando no copo
Adicionando algumas gotas de limão
*/
}
}

 

Destrinchando a chamada a preparar() da variável cafe temos:

  1. Invoca preparar() que está definido em BebidaCafeinada;
  2. Invoca esquentarAgua() que está definido em BebidaCafeinada;
  3. Invoca mistura() que está definido na classe especializada, Cafe;
  4. Invoca colocarEmCopo() que está definido em BebidaCafeinada;
  5. Invoca addCondimentos() que está definido na classe especializada, Cafe.

E assim prossegue nos mesmos moldes para a instância de Cha.

É muito importante que você saiba a partir desse ponto o que é um método template: ele apenas defini a ordem exata das chamadas de métodos no algoritmo.

Antes de prosseguir com o exemplo alterado para também suportar métodos gancho, vamos a um princípio que é mais evidente quando estamos utilizando o padrão Template Method.

Sem risos, o princípio conhecido como: Princípio Hollywood.

What?

Isso mesmo. Basicamente esse princípio diz: "Não me chame, deixa que eu te chamo quando for necessário." Ele ajuda a solucionar o problema de dependências múltiplas (quando bagunçadas) em um código.

Qual problema? Dependências múltiplas?

Quando seu projeto tem classes que dependem de classes de mais alto nível e essas de outras classes de mais baixo nível e essas, então, de classes irmãs. Um mar de dependências que podem levar qualquer projeto ao caos.

O princípio Hollywood implica em: as classes de mais alto nível serem responsáveis pela invocação de dependência das classes de mais baixo nível, realizando uma analogia aos altos managers de Hollywood que são os que têm o poder de chamar quem quiserem para os papéis nos filmes.

Se voltar ao projeto de café e chá, notará que a superclasse BebidaCefeinada é que invoca os métodos definidos nas classes especializadas, Cafe e Cha. Ou seja, essa classe de alto nível invoca os métodos das classes de mais baixo nível somente quando ela precisa deles.

Agora você pergunta: eu realmente tenho que saber esse princípio Hollywood? Ele não pareceu tão importante quanto o “De prioridade a composição ao invés de herança".

Você tem razão. Esse princípio, sem rodeios, é menos importante quando comparado ao real entendimento do padrão Template Method.

Se conseguiu compreender os benefícios desse padrão e como implementá-lo, já é o suficiente (calma que ainda há o exemplo com método gancho).

Eu sei que muitas vezes "menos é mais", porém optei por colocar o princípio Hollywood aqui, pois em algumas literaturas você vai encontrá-lo, dessa forma não vai ficar como um "índio", sem saber do que se trata.

Com isso podemos seguramente modificar a classe BebidaCafeinada para ter também um método gancho. Segue:

public abstract class BebidaCafeinada {
public final void preparar(){
esquentarAgua();
mistura();
colocarEmCopo();

if( temCondimentos() ){
addCondimentos();
}
}
...

public boolean temCondimentos(){ /* MÉTODO GANCHO */
return true;
}
}

 

Veja que se a subclasse não sobrescrever o método temCondimentos() ela terá sim condimentos quando na execução do software. Isso é um método gancho, ele tem ou uma implementação padrão (como no caso acima) ou é vazio.

Vamos a nova configuração da classe Cha, em nosso exemplo ela não terá condimentos:

public class Cha extends BebidaCafeinada {
...

@Override
public boolean temCondimentos() {
return false;
}
}

 

No caso acima você poderia implementar algo mais sofisticado, ou seja, um método interno a classe Cha perguntando ao cliente se ele quer ou não condimentos. Para abreviar o exemplo, aqui vamos somente retornar o valor booleano, false.

Executando novamente o código cliente temos:

public class ClienteCafeteria {
public static void main( String args[] ){
Cafe cafe = new Cafe();
Cha cha = new Cha();

cafe.preparar();
/*
Esquentando água
Moendo café
Colocando colocando no copo
Adicionando açúcar e leite
*/

cha.preparar(); /* SEM CONDIMENTOS */
/*
Esquentando água
Preparando o saquinho de chá
Colocando no copo
*/
}
}

 

Métodos gancho também podem trabalhar como uma espécie de hackcode (para não falar gambiarra). Nesse caso pode ser que apenas um passo a mais em uma classe do projeto esteja travando a aplicação do Template Method. Nesse impasse transforme esse passo em um método gancho, somente as subclasses que precisam desse método vão sobrescreve-lo.

Agora vamos a uma variação do padrão Template Method e que provavelmente você vai encontrar por ai. Essa variação não envolve herança, mas não deixa de ter um método template.

Vamos adicionar um método abstrato, getPreco(), a classe BebidaCafeinada:

public abstract class BebidaCafeinada {
public final void preparar(){
esquentarAgua();
mistura();
colocarEmCopo();

if( temCondimentos() ){
addCondimentos();
}
getPreco();
}
...

public abstract double getPreco();
}

 

Logo depois as devidas implementações em Cafe:

public class Cafe extends BebidaCafeinada {
...

@Override
public double getPreco() {
return 0.75;
}
}

 

E em Cha:

public class Cha extends BebidaCafeinada {
...

@Override
public double getPreco() {
return 1.25;
}
}

 

Ok, mas cadê a variação?

Ela vem agora no código cliente. Segue:

public class ClienteCafeteria {
public static void main( String args[] ){
BebidaCafeinada[] bebidaCafeinada = new BebidaCafeinada[2];
Cafe cafe = new Cafe();
Cha cha = new Cha();

bebidaCafeinada[0] = cha;
bebidaCafeinada[1] = cafe;

/* sort() É O MÉTODO TEMPLATE */
Arrays.sort(bebidaCafeinada, new Comparator<BebidaCafeinada>() {
@Override
public int compare(BebidaCafeinada o1, BebidaCafeinada o2) {
if( o1.getPreco() < o2.getPreco() ){
return -1;
}
else if( o1.getPreco() > o2.getPreco() ){
return 1;
}
return 0;
}
});

for( BebidaCafeinada b : bebidaCafeinada ){
System.out.print( b.getClass().getName()+": " );
System.out.println( b.getPreco() );
}
/* RESULTADO DO PRINT ACIMA
Cafe: 0.75
Cha: 1.25
*/
}
}

 

Como o método sort() foi feito para trabalhar com matrizes, de forma genérica e sabendo também que essas, as matrizes, não são passíveis de serem estendidas, em Java. O método sort() foi mantido como um método template, porém um método estático de Arrays para poder trabalhar com qualquer matriz. Ou seja, um Template Method sem subclasses no esquema.

A implementação de Comparator nos permite fornecer ao método template, sort(), o passo equivalente a um método abstrato implementado por subclasses no modelo convencional de implementação do padrão Template.

O código acima é um clássico exemplo de variação de padrão proposto aqui, algo possível com qualquer outro padrão de projeto.

Ponto negativo

  • Se muitos métodos da classe que contém o método template forem abstratos, isso pode atrapalhar o projeto em termos de consumo de memória.

Pontos positivos

  • Padrão de projeto quando aplicado, cria uma linguagem universal para os programadores, aumentando assim a eficiência na leitura do código;
  • Remove substancialmente o número de linhas de código duplicadas.

Conclusão

Esse padrão é daqueles que pouco têm itens contra sua implementação, até porque o principal objetivo dele é remover código duplicado. Logo, enxergada a oportunidade, aplique o Template Method.

Lembrando que esse padrão vai permitir definir os passos exatos de um algoritmo (método) onde alguns desses passos, mais específicos, são implementados por subclasses.

Quanto ao princípio Hollywood. Apesar da pouco importância é válido conhecer sobre, até porque é, mesmo assim, um princípio de orientação a objetos.

Fontes

Use a Cabeça! Padrões de Projetos.

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

Padrão de Projeto: Objeto NuloPadrão de Projeto: Objeto NuloAndroid
Padrão de Projeto: State (Estado)Padrão de Projeto: State (Estado)Android
Padrão de Projeto: Decorator (Decorador)Padrão de Projeto: Decorator (Decorador)Android
Padrão de Projeto: SingletonPadrão de Projeto: SingletonAndroid

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...
12/09/2016, Segunda-feira, às 16h
Show. Gostei ! É muito comum enxergar durante uma codificação q seu projeto precisa de Tamplate Method ! Valeu Thiengo.
Responder
Vinícius Thiengo (0) (0)
13/09/2016, Terça-feira, às 01h
Fala Paulo, blz?
Realmente é excelente padrão, daqueles que é bom o coder conhecer. Abraço
Responder