ComportamentalIniciante6 min de leitura9 seções1994, Marco de referência: a ideia é mais velha que a data

Template Method

Define o esqueleto de um algoritmo numa classe base e deixa que as subclasses preencham passos específicos, sem poder alterar a ordem em que eles acontecem.

Em 10 segundos

A classe base escreve o roteiro — ler, validar, gravar, notificar — e deixa buracos para as subclasses preencherem. Quem estende decide o COMO de cada passo, nunca a ORDEM: essa fica trancada num lugar só.

  • #heranca
  • #esqueleto
  • #ganchos
  • #gof
Analogia

A receita-base do pão

Toda receita de pão segue a mesma sequência: misturar, sovar, fermentar, assar. O que muda entre o pão francês e a focaccia é o recheio de cada etapa — a farinha, o tempo de fermentação, a temperatura. Nenhum padeiro assa antes de fermentar. A sequência é a receita-base; as variações preenchem os espaços.

01Conceito

O problema

Processos irmãos costumam compartilhar mais do que aparentam: a ordem das etapas é igual, e só o miolo de cada uma muda conforme o formato, o cliente ou o canal.

Duplicar a sequência espalha uma decisão que deveria ser única. Quando um passo novo entra (uma checagem de segurança, por exemplo), é preciso lembrar de todas as cópias.

O padrão aplica o princípio de Hollywood — 'não nos chame, nós chamamos você'. A inversão é o ponto: a base controla o fluxo e chama a subclasse nos momentos certos, em vez de a subclasse orquestrar a base. É o mesmo mecanismo que faz um framework ser framework, e não biblioteca.

A comparação com Strategy é o eixo da decisão. Template Method varia por herança, em tempo de compilação: uma subclasse por variação, e o objeto não muda de comportamento depois de criado. Strategy varia por composição, em tempo de execução: você injeta o algoritmo e pode trocá-lo. Template Method economiza cerimônia quando as variações são poucas e estáveis; Strategy paga essa cerimônia para ganhar flexibilidade.

Vale notar o parentesco com Factory Method: ele é, na prática, um Template Method cujo passo variável é 'qual objeto criar'. Reconhecer isso evita tratá-los como coisas totalmente distintas.

02Conceito

Passos obrigatórios, padrões e ganchos

Nem todo buraco é igual. Um passo pode ser abstrato (a subclasse é obrigada), ter implementação padrão (sobrescrever é opcional) ou ser um gancho vazio (existe só para quem quiser se pendurar).

Abstrato comunica 'sem isto o algoritmo não existe' — ler e gravar, no exemplo. O compilador cobra, o que é bom: a subclasse não nasce pela metade.

Padrão razoável cobre o caso comum e permite especializar. validar filtrando linhas vazias serve para quase todos; quem tem regra própria sobrescreve. Reduz o atrito de criar uma variação nova.

Gancho vazio (aoTerminar) não faz nada e existe para observação ou ajuste fino. É o mais barato de oferecer e o mais fácil de abusar: cada gancho é API pública que você promete manter.

A regra prática é ser econômico. Cada ponto de extensão amplia o contrato com quem estende, e reduzir ganchos depois é quebra de compatibilidade.

Como se organiza
  • ImportadorRelatorio.importar()a ordem mora aqui e não é sobrescrita

    • 1. ler()abstrato — cada formato lê do seu jeito

    • 2. validar()padrão razoável — sobrescrever é opcional

    • 3. gravar()abstrato — obrigatório

    • 4. aoTerminar()gancho vazio — só para quem quiser

Os passos estão dentro do método-template, não ao lado dele: é isso que impede uma subclasse de trocar a ordem. Tracejado marca o que é opcional preencher.
Sem o padrão × com o padrão

Com Template Method: a ordem é única

  • a base declara a sequência uma vez
  • subclasses preenchem só o que lhes é próprio
  • passo novo entra na base e vale para todos
  • o compilador cobra os passos obrigatórios

O custo é herança: a variação fica presa em tempo de compilação e a base vira contrato difícil de mudar depois que muita gente estendeu.

O padrão troca duplicação por acoplamento hierárquico. Compensa quando a ordem é estável e as variações são poucas — se as duas coisas mudam, Strategy envelhece melhor.
Passo a passoO fluxo, passo a passo
  1. 1

    Escrever o esqueleto

    Na base, o método-template chama os passos na ordem certa e não deve ser sobrescrito.

  2. 2

    Abrir os buracos

    Declare passos abstratos (obrigatórios), com padrão razoável ou ganchos vazios.

  3. 3

    Preencher nas subclasses

    Cada variação implementa só o que lhe é próprio — sem reordenar o fluxo.

  4. 4

    Chamar o template

    O cliente invoca importar(); a base orquestra e chama os passos no momento certo.

03Anatomia

Navegue pelas camadas

Camada 1/3O método-template

Camada 1

O método-template

o roteiro, fechado para alteração

Implementa o algoritmo chamando os passos na ordem. Deve ser marcado como final (ou documentado como não-sobrescrevível): permitir que alguém o substitua devolve a ordem ao caos que o padrão veio resolver.

No código

importar() { ler(); validar(); gravar(); aoTerminar(); }

Se violar: subclasse que sobrescreve o próprio template method esvazia o padrão — cada uma volta a ter a sua ordem.

04Estrutura

Estrutura

Diagrama de classes
05Implementação

Código

abstract class ImportadorRelatorio {
  // O template method: a ordem e final, ninguem sobrescreve
  importar(caminho: string): number {
    const linhas = this.ler(caminho);
    const validas = this.validar(linhas);
    this.gravar(validas);
    this.aoTerminar(validas.length); // gancho opcional
    return validas.length;
  }

  // passos obrigatorios: cada formato le e grava do seu jeito
  protected abstract ler(caminho: string): string[];
  protected abstract gravar(linhas: string[]): void;

  // passo com padrao razoavel: a subclasse sobrescreve se quiser
  protected validar(linhas: string[]): string[] {
    return linhas.filter((l) => l.trim().length > 0);
  }

  // gancho: nao faz nada por padrao
  protected aoTerminar(_total: number): void {}
}

class ImportadorCSV extends ImportadorRelatorio {
  protected ler(caminho: string): string[] {
    return ["a,1", "b,2"]; // leria o arquivo
  }
  protected gravar(linhas: string[]): void {
    console.log("gravando", linhas.length, "linhas");
  }
}

new ImportadorCSV().importar("vendas.csv");
06No mundo real

Casos de uso

Ciclo de vida em framework de UI

Cenário

Um componente precisa ser montado, renderizado, atualizado e destruído sempre na mesma ordem, e o autor do componente só quer dizer o que fazer em cada momento.

Como o padrão entra

O framework implementa o ciclo e chama métodos de ciclo de vida do componente nos pontos certos. É Template Method em escala: o fluxo pertence ao framework, o conteúdo ao desenvolvedor.

Trade-off: Os ganchos viram API pública e quase imutável — mudar quando um deles é chamado quebra todo o ecossistema, o que empurra frameworks a acumular métodos legados por anos.

Pipeline de importação de arquivos

Cenário

Um ERP importa planilhas de vendas em CSV, XML e um formato posicional legado; todos passam por leitura, validação, deduplicação e gravação transacional.

Como o padrão entra

A base implementa a sequência e a transação; cada formato só implementa o parser e o mapeamento. Adicionar um formato novo é uma classe pequena, sem risco de esquecer a deduplicação.

Trade-off: Quando um formato precisa de um passo extra fora da sequência (o legado exige reconciliação antes de validar), a tentação é adicionar mais um gancho — e a base incha até virar o problema.

Casos de teste com preparação comum

Cenário

Uma suíte de integração precisa subir banco, aplicar migrações, rodar o teste e limpar tudo, sempre nessa ordem, para dezenas de cenários.

Como o padrão entra

A classe-base de teste implementa setup e teardown ao redor de um método abstrato com o corpo do caso. É o padrão por trás de praticamente todo framework de teste xUnit.

Trade-off: Hierarquias de classes-base de teste tendem a crescer em profundidade, e depois de três níveis fica difícil saber qual setup rodou — a herança que economizava passa a esconder.

O template que a subclasse reescreve

Compila, roda, passa no code review. E quebra depois.

A classe base existe, mas o método-template virou hook aberto: cada subclasse redefine a ordem inteira. O padrão vira herança comum sem a invariante que justificava o esqueleto.

abstract class ImportadorRelatorio {
  // O "template" deixou de ser final — cada subclasse reescreve a ordem.
  importar(caminho: string): number {
    return this.rodar(caminho);
  }

  protected abstract rodar(caminho: string): number;
}

class ImportadorCSV extends ImportadorRelatorio {
  protected rodar(caminho: string): number {
    const linhas = this.ler(caminho);
    this.gravar(linhas);          // <- validar sumiu
    return linhas.length;
  }
  protected ler(c: string) { return ["a,1"]; }
  protected gravar(_l: string[]) {}
}

// A ordem unica morreu. Cada formato tem a sua sequencia —
// e o bug "esquecemos de validar" volta a ser possivel.

O que quebra

  • Ao corrigir um passoA correção na base não alcança quem sobrescreveu o fluxo — as cópias divergem de novo.
  • Na auditoriaUm formato 'esquece' validar e ninguém percebe, porque a ordem não é única.
  • Na leituraPara entender o algoritmo, é preciso abrir cada subclasse — o esqueleto sumiu.

A saída: Marque o template como final (ou documente como não-sobrescrevível). Subclasse preenche passos e ganchos — nunca a ordem. Se a ordem precisa variar, o padrão certo é outro (Strategy).

08Cuidado

Armadilhas

  1. Base que vira depósito

    Cada necessidade nova de uma subclasse vira um gancho a mais na base. Depois de um tempo, a classe abstrata tem quinze métodos protegidos, metade deles usada por uma única subclasse. O sinal de alerta é um gancho com um só implementador: aquilo provavelmente não pertence à base.

  2. Herança onde cabia composição

    Template Method consome a única herança disponível em linguagens sem herança múltipla e amarra a variação em tempo de compilação. Se as variações se combinam (formato × destino × política de erro), a hierarquia explode em classes cruzadas. Aí o padrão certo é Strategy: injetar as partes em vez de herdá-las.

  3. Chamar métodos sobrescrevíveis no construtor

    Se o construtor da base chama um passo que a subclasse implementa, ele executa antes de os campos da subclasse serem inicializados — e lê undefined/null sem erro aparente. É um bug clássico de herança, e o Template Method o convida ao juntar fluxo e hierarquia.

09Decisão

Quando usar × quando evitar

Use quando

3
  • A sequência é estável e as variações são pontuais.
  • A ordem é uma invariante que não deve ficar a cargo de quem estende.
  • Você está oferecendo pontos de extensão explícitos num framework.

Evite quando

3
  • As variações se combinam e a hierarquia explodiria — prefira Strategy.
  • As subclasses precisam mudar a ordem, não só o conteúdo.
  • A base já acumula ganchos usados por um implementador só.

Testar o que ficou

3 armadilhas de Template Method viram perguntas, com o nome do padrão escondido, misturadas com padrões parecidos.

Onde isto aparece de verdade

Você já usa isto, talvez sem saber o nome.

  • beforeEach / it / afterEachO framework de teste define o esqueleto da execução; você preenche os passos.
  • Hooks de ciclo de vidaO framework decide quando montar, atualizar e desmontar; você diz o que acontece em cada etapa.

O padrão em uma linha

Sem nome de domínio, sem cerimônia. Só a ideia.

// Esqueleto na base; passos nas subclasses.
class Teste { run() { this.setup(); this.test(); this.teardown(); } }

O que ele cobra

1 salto a mais entre a chamada e o efeito.

  • A herança amarra a subclasse ao esqueleto do pai, que não muda sem impacto abaixo
  • O fluxo se lê pela metade na superclasse e pela metade nas subclasses

Não vale se: os passos variáveis não compartilham um esqueleto comum — aí compor com Strategy acopla menos que herdar.

Costuma ser confundido com

Onde isto aparece

Continue por aqui