Design Patterns: Template Method (Parte 8)

·3 min de leitura

Contexto

Depois do Observer (Parte 7), vamos a outro padrão comportamental: Template Method. Ele existe para um tipo específico de repetição: quando você tem um processo com uma sequência estável (passos que sempre ocorrem na mesma ordem), mas alguns desses passos variam conforme o cenário.

Sem o padrão, essa sequência tende a ser copiada em várias classes — e aí qualquer mudança “no esqueleto” vira manutenção repetida. Com Template Method, a sequência fica definida em um único lugar (a classe base) e as variações ficam isoladas em métodos que as subclasses implementam ou sobrescrevem.

É também aqui que Template Method e Strategy costumam ser confundidos. Template Method fixa o fluxo via herança: a classe base controla o template. Strategy compõe o algoritmo via interface, o que costuma ser mais flexível para trocar em runtime. Herança não é “errada”, mas é mais rígida e por isso Template Method aparece muito em frameworks, pipelines e bibliotecas que precisam oferecer pontos de extensão bem definidos.

Exemplo na prática

Imagine um job de importação com passos comuns:

<?php

abstract class ImportJob
{
    /** Template Method: a sequência é fixa aqui. */
    final public function run(string $sourcePath): void
    {
        $raw = $this->readSource($sourcePath);
        $rows = $this->parse($raw);
        $this->validate($rows);
        $this->persist($rows);
        $this->afterImport($rows);
    }

    private function readSource(string $path): string
    {
        return file_get_contents($path) ?: throw new RuntimeException('Falha ao ler arquivo.');
    }

    /** Passo variável: cada importador define o parser. */
    abstract protected function parse(string $raw): array;

    /** Hook opcional: default não faz nada. */
    protected function validate(array $rows): void {}

    /** Passo variável: estratégia de persistência. */
    abstract protected function persist(array $rows): void;

    protected function afterImport(array $rows): void {}
}

final class CsvUserImport extends ImportJob
{
    protected function parse(string $raw): array
    {
        $lines = explode("\n", trim($raw));
        $header = str_getcsv(array_shift($lines) ?: '');

        $out = [];
        foreach ($lines as $line) {
            $cols = str_getcsv($line);
            $out[] = array_combine($header, $cols);
        }

        return $out;
    }

    protected function validate(array $rows): void
    {
        foreach ($rows as $row) {
            if (!isset($row['email'], $row['name'])) {
                throw new InvalidArgumentException('CSV inválido: faltam colunas obrigatórias.');
            }
        }
    }

    protected function persist(array $rows): void
    {
        // ... grava no banco ...
    }
}

O papel do run() é ser o Template Method: ele declara a sequência e, ao ser final, evita que uma subclasse reordene o processo sem perceber. Os passos variáveis ficam concentrados em parse() e persist(), enquanto validate() e afterImport() funcionam como hooks: a base oferece um comportamento padrão (às vezes “não faz nada”) e a subclasse só sobrescreve quando precisa.

Cuidados

O cuidado principal é não transformar Template Method em uma árvore profunda de herança: isso costuma ficar frágil. E se quase tudo varia, talvez Strategy seja um encaixe melhor. Sobre final: alguns times não gostam, mas ele evidencia a intenção do GoF de fixar a estrutura do algoritmo; se você abrir a sequência demais, o padrão perde a força.

Conclusão

Template Method é ótimo quando você tem pipeline com etapas estáveis e pontos de extensão claros. Ele documenta o processo no código — a sequência fica explícita na classe base.

Referências

  • Gamma, E. et al. Design Patterns (GoF).
  • Refactoring Guru – Template Method: https://refactoring.guru/design-patterns/template-method

Links