Padrão de Criação (GoF)

Factory Method

Define uma interface para criar um objeto, mas deixa as subclasses (ou implementações) decidirem qual classe concreta instanciar — desacoplando o código que usa o objeto do código que o cria.

Intenção

Definir uma interface para criar um objeto, delegando às subclasses (ou implementações concretas) a decisão de qual classe instanciar. O Factory Method permite que uma classe adie a instanciação para suas subclasses, promovendo o Princípio do Aberto/Fechado: para suportar um novo tipo de produto, basta criar uma nova subclasse — sem modificar o código existente.

Catalogado por Gamma, Helm, Johnson e Vlissides no livro Design Patterns: Elements of Reusable Object-Oriented Software (1994), o Factory Method pertence à categoria de padrões de criação. É um dos padrões mais usados na prática e serve de base para o Abstract Factory.

Problema

Imagine um sistema de notificações que, inicialmente, só enviava e-mails. Com o crescimento do produto, surgiu a necessidade de enviar SMS e, depois, notificações push. A solução ingênua — um if/else ou switch dentro do código de negócio escolhendo qual classe de notificação instanciar — viola dois princípios fundamentais:

  • Aberto/Fechado: cada novo canal exige modificar código já existente e testado, arriscando regressões.
  • Responsabilidade Única: a classe de negócio acumula a decisão de criação junto com a lógica de uso.

O problema se agrava quando a instanciação envolve configuração complexa (ex.: autenticação SMTP, credenciais de API, pooling de conexões). Espalhar essa lógica pelo código torna qualquer mudança de provider uma caçada de múltiplos arquivos.

Simple Factory vs Factory Method

Vale distinguir dois conceitos que frequentemente se confundem:

  • Simple Factory (não é um padrão GoF): uma classe estática ou função com um switch que retorna instâncias diferentes. É simples e suficiente quando os tipos são fixos e raramente mudam. O problema: continua violando Aberto/Fechado quando novos tipos surgem.
  • Factory Method (padrão GoF): define um método de criação em uma classe base (abstrata ou concreta) e deixa subclasses sobrescreverem esse método para fornecer o produto concreto. Extensível sem modificar o código base.

Use Simple Factory quando os tipos são poucos e estáveis; prefira Factory Method quando a variação de tipos é o ponto de extensão central do seu design.

Solução

O Factory Method organiza o código em quatro participantes:

  1. Product (interface/classe abstrata): define o contrato que todos os produtos concretos devem cumprir. Ex.: interface Notificacao com o método enviar(destinatario, mensagem).
  2. ConcreteProduct: implementa o contrato do Product. Ex.: EmailNotificacao, SmsNotificacao, PushNotificacao.
  3. Creator (classe base): declara o factory method — criarNotificacao() — e pode conter lógica de negócio que usa o produto retornado pelo método. O Creator não sabe qual ConcreteProduct será criado.
  4. ConcreteCreator: sobrescreve o factory method para instanciar e retornar um ConcreteProduct específico.

O código cliente trabalha com o Creator e o Product por meio de interfaces — nunca referencia diretamente as classes concretas. Para adicionar um novo canal de notificação, basta criar um novo par ConcreteProduct + ConcreteCreator.

Estrutura

Diagrama UML simplificado com o exemplo de notificações:

          «interface»
         Notificacao
    ┌──────────────────────┐
    │ + enviar(dest, msg)  │
    └──────────────────────┘
              ▲
    ┌─────────┴──────────────────────┐
    │                                │
EmailNotificacao           SmsNotificacao
(ConcreteProduct)          (ConcreteProduct)


          «abstract»
       NotificacaoCreator
    ┌──────────────────────────────────┐
    │ # criarNotificacao(): Notificacao│  ← factory method (abstrato)
    │ + notificar(dest, msg): void     │  ← usa o produto via interface
    └──────────────────────────────────┘
              ▲
    ┌─────────┴────────────────────────┐
    │                                  │
EmailCreator                    SmsCreator
(ConcreteCreator)               (ConcreteCreator)
criarNotificacao()              criarNotificacao()
  → new EmailNotificacao()        → new SmsNotificacao()


Fluxo de chamada:

  cliente
    │
    │  emailCreator.notificar("a@b.com", "Olá")
    ▼
  NotificacaoCreator.notificar()
    │
    │  notif = this.criarNotificacao()   ← chama o factory method
    │                                       (polimorfismo resolve para EmailNotificacao)
    │  notif.enviar("a@b.com", "Olá")
    ▼
  EmailNotificacao.enviar()

Exemplos de código

Exemplo 1 — Sistema de notificações

O Creator define o contrato e a lógica de negócio; cada ConcreteCreator fornece seu produto. O código cliente só conhece a classe base.

// ── Product ──────────────────────────────────────────────────
interface Notificacao {
  enviar(destinatario: string, mensagem: string): void;
}

// ── ConcreteProducts ─────────────────────────────────────────
class EmailNotificacao implements Notificacao {
  enviar(destinatario: string, mensagem: string): void {
    console.log(`[EMAIL] Para: ${destinatario} | Mensagem: ${mensagem}`);
  }
}

class SmsNotificacao implements Notificacao {
  enviar(destinatario: string, mensagem: string): void {
    console.log(`[SMS] Para: ${destinatario} | Mensagem: ${mensagem}`);
  }
}

class PushNotificacao implements Notificacao {
  enviar(destinatario: string, mensagem: string): void {
    console.log(`[PUSH] Para: ${destinatario} | Mensagem: ${mensagem}`);
  }
}

// ── Creator ──────────────────────────────────────────────────
// O factory method é abstrato: a subclasse é obrigada a fornecê-lo.
abstract class NotificacaoCreator {
  // Factory Method — cada subclasse decide qual produto criar.
  protected abstract criarNotificacao(): Notificacao;

  // Lógica de negócio reutilizada por todos os creators.
  // Usa o produto via interface — sem conhecer a classe concreta.
  public notificar(destinatario: string, mensagem: string): void {
    const notificacao = this.criarNotificacao();
    notificacao.enviar(destinatario, mensagem);
  }
}

// ── ConcreteCreators ─────────────────────────────────────────
class EmailCreator extends NotificacaoCreator {
  protected criarNotificacao(): Notificacao {
    return new EmailNotificacao();
  }
}

class SmsCreator extends NotificacaoCreator {
  protected criarNotificacao(): Notificacao {
    return new SmsNotificacao();
  }
}

class PushCreator extends NotificacaoCreator {
  protected criarNotificacao(): Notificacao {
    return new PushNotificacao();
  }
}

// ── Código cliente ───────────────────────────────────────────
// O cliente trabalha com NotificacaoCreator — sem acoplar-se
// às classes concretas de produto.
function enviarAlerta(creator: NotificacaoCreator, msg: string): void {
  creator.notificar("usuario@exemplo.com", msg);
}

enviarAlerta(new EmailCreator(), "Sua fatura está disponível.");
enviarAlerta(new SmsCreator(),   "Código de verificação: 4821");
enviarAlerta(new PushCreator(),  "Novo comentário no seu post.");

Exemplo 2 — Factory Method com parâmetro (variante sem subclasse)

Em linguagens com suporte a funções de primeira classe (TypeScript) ou closures, o factory method pode ser parametrizado — evitando a explosão de subclasses para casos simples. Esta variante é menos ortodoxa, mas muito comum na prática:

type Canal = "email" | "sms" | "push";

// Registro de factories: cada canal mapeia para uma função criadora.
// Adicionar um novo canal = registrar uma nova entrada no mapa.
const factories: Record<Canal, () => Notificacao> = {
  email: () => new EmailNotificacao(),
  sms:   () => new SmsNotificacao(),
  push:  () => new PushNotificacao(),
};

function criarNotificacao(canal: Canal): Notificacao {
  const factory = factories[canal];
  return factory();
}

// ── Uso ──────────────────────────────────────────────────────
const canais: Canal[] = ["email", "sms", "push"];

for (const canal of canais) {
  const notif = criarNotificacao(canal);
  notif.enviar("user@exemplo.com", `Alerta via ${canal}`);
}

Quando usar

  • O tipo do objeto a criar não é conhecido em tempo de compilação: ele depende de configuração, do ambiente ou de entrada do usuário — e novas variações surgirão no futuro.
  • Você quer que o código cliente seja independente das classes concretas que ele cria — desacoplando o "uso" da "criação".
  • Você quer fornecer pontos de extensão: bibliotecas e frameworks usam Factory Method para permitir que o consumidor da biblioteca substitua componentes internos sem alterar o código-fonte do framework.
  • A criação envolve lógica reutilizável: se a lógica de negócio que usa o produto (o método notificar do Creator) é idêntica para todos os produtos, só variando a instanciação, o Factory Method elimina duplicação via herança ou composição.

Quando evitar

  • Quando um Simple Factory resolve: se os tipos de produto são poucos, estáveis e conhecidos em tempo de compilação, um switch numa função de fábrica é mais simples e igualmente legível — sem a cerimônia de subclasses.
  • Quando não há variação real: se há apenas um tipo de produto concreto agora e no futuro previsível, a abstração é prematura.
  • Em projetos com DI containers: NestJS, Spring, Laravel e similares já resolvem a seleção de implementações via injeção de dependência e configuração — reimplementar Factory Method manualmente costuma ser redundante.

Prós e contras

Prós

  • Segue o Princípio do Aberto/Fechado — novos tipos de produto não exigem modificar código existente.
  • Desacopla o código cliente das classes concretas de produto.
  • Centraliza a lógica de criação, eliminando instanciações espalhadas pelo código.
  • Facilita testes: o Creator pode ser testado com um produto fake injetado via subclasse de teste.
  • Permite que subclasses reutilizem a lógica de negócio do Creator sem repetição.

Contras

  • Introduz hierarquia de classes: para cada novo ConcreteProduct, geralmente é necessário um novo ConcreteCreator — pode levar à explosão de subclasses.
  • Mais complexo que um Simple Factory para casos onde os tipos são fixos e poucos.
  • Pode ser difícil de entender para quem não conhece o padrão, pois o fluxo de criação não é imediato na leitura do código cliente.

Armadilhas comuns

1. Explosão de subclasses

Se cada variação de produto exige um novo par Creator + Product, e a aplicação tem dezenas de variações, a hierarquia cresce rapidamente. Quando os creators não têm lógica de negócio própria (são vazios além do factory method), avalie substituir a hierarquia por um mapa de factories (como no Exemplo 2) ou por um Abstract Factory se os produtos forem relacionados em famílias.

2. Confundir Simple Factory com Factory Method

Atenção: Simple Factory é um idioma de programação, não um padrão GoF. Um método estático Notificacao::criar("email") que faz switch($tipo) resolve muitos problemas práticos sem a cerimônia de subclasses. Aplique Factory Method apenas quando a extensão via subclasse for o objetivo real — caso contrário, você adiciona complexidade sem benefício proporcional.

3. Factory Method vs Abstract Factory

Factory Method cria um único produto — a variação está em qual classe concreta desse produto é instanciada. Abstract Factory cria famílias de produtos relacionados — vários objetos que devem funcionar juntos. Se você se viu criando vários factory methods relacionados no mesmo Creator, provavelmente o design pede um Abstract Factory.

4. Lógica de seleção escapando para o cliente

Um erro comum: o código cliente escolhe qual ConcreteCreator instanciar com um if/switch baseado em configuração. Isso derrota o propósito do padrão. A seleção do creator concreto deve ocorrer o mais próximo possível da borda da aplicação (ponto de entrada, configuração, container de DI), não espalhada pela lógica de negócio.

Padrões relacionados

O Factory Method é a base de vários outros padrões e frequentemente aparece em conjunto com eles:

O Singleton é frequentemente implementado junto ao Creator quando a fábrica em si precisa ser única na aplicação. O Abstract Factory pode ser visto como um grupo de Factory Methods coordenados para criar famílias de produtos compatíveis — enquanto o Factory Method foca em um único tipo de produto, o Abstract Factory coordena a criação de múltiplos tipos que devem trabalhar juntos. O Template Method compartilha a mesma estrutura de herança: a classe base define o esqueleto do algoritmo com passos abstratos, e as subclasses fornecem as implementações concretas — o factory method é, essencialmente, um Template Method especializado em criação de objetos.