Design Patterns: Template Method (Parte 8)
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