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

Factory Method

Define uma interface para criar objetos, deixando as subclasses decidirem qual classe concreta instanciar.

Em 10 segundos

Em vez de o cliente dar new na classe concreta, uma classe criadora declara um método de criação que as subclasses sobrescrevem. O cliente trabalha só com a interface do produto — e tipos novos entram sem alterar código existente.

  • #polimorfismo
  • #gof
  • #desacoplamento
Analogia

A linha de montagem da fábrica

A fábrica define a esteira e as etapas de montagem uma única vez. Cada unidade especializada decide qual modelo sai da linha — sedã, hatch ou picape — sem que o processo geral precise mudar. O cliente pede 'um carro' e recebe o produto certo; quem escolhe o modelo concreto é a subclasse, não quem faz o pedido.

01Conceito

O problema

O código cliente precisa criar objetos, mas não deveria depender das classes concretas. Cada novo tipo obrigaria a alterar o cliente, violando o princípio Aberto/Fechado.

Um new ClasseConcreta() espalhado pelo código acopla o cliente a implementações específicas e dificulta tanto os testes quanto futuras extensões.

A raiz está numa assimetria: você programa contra interfaces em todo lugar — parâmetros, retornos, campos — menos na hora de instanciar. O new é o único ponto do polimorfismo que não é polimórfico: ele exige o nome da classe concreta ali, em tempo de compilação. Todo lugar que escreve new fica amarrado àquela implementação, e a criação é justamente o detalhe que mais tende a variar (por plataforma, ambiente, formato, plano do cliente).

O padrão resolve movendo o new para um método que subclasses sobrescrevem — e é aí que ele se diferencia das alternativas. Uma função factory simples (criarBotao(tipo)) centraliza a escolha num switch e basta para casos diretos; um container de injeção de dependência resolve quando a decisão é de configuração. Factory Method vale quando a criação depende de um contexto que a subclasse conhece e o método criador participa de um algoritmo maior da classe base — o clássico par com Template Method.

Também não confunda com Abstract Factory: o Factory Method cria um produto por vez, via herança; o Abstract Factory agrupa a criação de uma família inteira de produtos relacionados (botão + input + modal do mesmo tema), via composição. Na prática, Abstract Factories costumam ser implementadas como um conjunto de factory methods.

Como se encaixa
  1. Cliente
  2. chama operacao()Criador
  3. delega a criaçãofactoryMethod()
  4. instanciaProduto concreto
O cliente nunca escreve new: a subclasse do criador decide qual produto concreto nasce.
Passo a passoO fluxo, passo a passo
  1. 1

    Declarar o factory method

    A classe/interface base declara um método de criação que retorna a abstração do produto.

  2. 2

    Sobrescrever na subclasse

    Cada subclasse implementa o método para devolver o produto concreto adequado.

  3. 3

    Chamar via abstração

    O cliente usa operacao(), que internamente chama o factory method — sem saber qual concreto vem.

  4. 4

    Estender sem alterar

    Um novo produto exige apenas uma nova subclasse; o cliente permanece intocado.

02Anatomia

Navegue pelas camadas

Camada 1/4Cliente

Camada 1

Cliente

usa o criador sem conhecer concretos

Conhece apenas a classe base do criador e a interface do produto. Chama operacao() e recebe o resultado — qual produto concreto foi instanciado é invisível para ele. É essa ignorância que permite trocar a plataforma, o banco ou o formato sem tocá-lo.

No código

function render(dialogo: Dialogo) {
  return dialogo.renderizar(); // não sabe qual botão sai
}

Se violar: um new BotaoWeb() no cliente esvazia o padrão: a dependência da classe concreta que você queria remover voltou, e com ela a mudança em cascata a cada tipo novo.

03Estrutura

Estrutura

Diagrama de classes
04Implementação

Código

interface Botao {
  render(): string;
}

class BotaoWeb implements Botao {
  render() { return "<button>"; }
}
class BotaoMobile implements Botao {
  render() { return "TouchableOpacity"; }
}

abstract class Dialogo {
  abstract criarBotao(): Botao;      // factory method
  renderizar() { return this.criarBotao().render(); }
}

class DialogoWeb extends Dialogo {
  criarBotao() { return new BotaoWeb(); }
}

new DialogoWeb().renderizar(); // "<button>"
05No mundo real

Casos de uso

SDK multi-plataforma

Cenário

Uma biblioteca de UI roda em web e mobile: o fluxo do diálogo (abrir, validar, confirmar) é idêntico, mas o botão é <button> num lado e TouchableOpacity no outro.

Como o padrão entra

Dialogo define renderizar() uma vez; DialogoWeb e DialogoMobile sobrescrevem apenas criarBotao(). Quem usa o SDK pode até criar DialogoDesktop próprio — o ponto de extensão é público.

Trade-off: Cada plataforma nova adiciona um par criador+produto; a hierarquia cresce em dupla e precisa de disciplina para não divergir.

Conexões de banco por ambiente

Cenário

A aplicação usa Postgres em produção, mas os testes precisam rodar em segundos com SQLite em memória, sem subir container.

Como o padrão entra

O repositório base declara criarConexao(): Conexao e escreve as queries contra a abstração; o ambiente decide qual subclasse entra. A suíte de testes ganha velocidade sem duplicar lógica de acesso.

Trade-off: Diferenças reais de SQL e de comportamento entre os bancos podem vazar pela abstração — o contrato só segura o que os dois suportam, e o teste em SQLite não prova tudo.

Parsers por formato de arquivo

Cenário

Um importador aceita CSV, XML e JSON de parceiros diferentes e precisa devolver os mesmos registros normalizados, com formatos novos chegando por contrato.

Como o padrão entra

Importador define o pipeline (ler, validar, gravar) e delega a criarParser(); cada formato vira uma subclasse. Formato novo = subclasse nova, pipeline intocado.

Trade-off: Formatos muito diferentes (streaming linha a linha vs documento inteiro em memória) forçam a interface do parser a nivelar por baixo ou a crescer demais.

A fábrica que conhece todos os produtos

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

Em vez de cada subclasse decidir o que criar, existe uma função estática com um switch que conhece todas as implementações. É o mesmo new espalhado de antes, agora concentrado num único ponto de acoplamento.

class FabricaDeRelatorio {
  static criar(tipo: string): Relatorio {
    switch (tipo) {
      case "pdf":   return new RelatorioPdf();
      case "csv":   return new RelatorioCsv();
      case "xlsx":  return new RelatorioXlsx();
      case "html":  return new RelatorioHtml();
      // ... e mais oito
      default: throw new Error("tipo desconhecido: " + tipo);
    }
  }
}

// Este arquivo importa TODAS as implementacoes.
// Consequencias:
// - formato novo = editar aqui (Aberto/Fechado violado)
// - o bundle carrega os doze, mesmo usando um
// - o erro de tipo desconhecido so aparece em runtime

O que quebra

  • Ao adicionar formatoUm arquivo central precisa ser editado sempre, e ele vira ponto de conflito de merge entre times.
  • No bundleTodas as implementações são importadas junto, inclusive as que aquele fluxo nunca vai usar.
  • Com tipo inválidoA falha acontece em tempo de execução, com uma string, em vez de em tempo de compilação.

A saída: Factory Method é polimorfismo: a subclasse que já sabe o contexto sobrescreve o método de criação. Quando o que se quer é mesmo mapear uma string vinda de fora, isso é um registro (Map<string, () => Produto>) preenchido por quem compõe a aplicação — e aí formato novo se registra, não edita.

Do cheiro ao padrão

Um passo por vez, com o motivo de cada um.

O cheiro: O new do exportador espalhado por cinco lugares, cada um decidindo o tipo por conta própria.

Como está. O new do exportador espalhado por cinco lugares, cada um decidindo o tipo por conta própria.

// relatorio-controller.ts
if (formato === "pdf") exportador = new ExportadorPdf(config);
else if (formato === "csv") exportador = new ExportadorCsv(config);

// agendador.ts — a mesma decisao, de novo
if (formato === "pdf") exportador = new ExportadorPdf(config);
else if (formato === "csv") exportador = new ExportadorCsv(config);

// E em outros tres arquivos. Formato novo = cacar todos.
08Cuidado

Armadilhas

  1. Factory para classe única (YAGNI)

    Se existe um único produto e nenhuma variação prevista, o factory method é cerimônia: classe abstrata, subclasse e override para embrulhar um new que uma função simples resolveria. Introduza o padrão quando a segunda variação aparecer de verdade — extrair a criação depois é uma refatoração barata.

  2. Factory que retorna o tipo concreto

    Declarar o método como criarBotao(): BotaoWeb em vez de retornar a interface mantém todo mundo acoplado à implementação — ganhou-se uma chamada a mais sem ganhar desacoplamento. O retorno do factory method é a abstração; se o chamador precisa saber o tipo exato, o padrão não está cumprindo o papel.

  3. Hierarquias paralelas explodindo

    Cada produto novo pede um criador novo: com dez formatos, são dez parsers e dez importadores. Se a subclasse do criador existe só para escolher o produto — sem lógica própria —, um registro (mapa de chave para construtor) ou Abstract Factory resolve com muito menos classe. Herança é o mecanismo do padrão, não um objetivo.

09Decisão

Quando usar × quando evitar

Use quando

3
  • Você não sabe de antemão os tipos exatos de objetos que o código vai precisar criar.
  • Quer permitir que usuários da sua biblioteca estendam os componentes internos.
  • Quer isolar a lógica de construção do resto da aplicação.

Evite quando

2
  • Só existe um tipo de produto e não há previsão de variação — vira complexidade inútil.
  • Uma simples função de criação ou injeção de dependência já resolve.

Testar o que ficou

3 armadilhas de Factory 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.

  • document.createElementVocê pede um elemento pelo nome e recebe a subclasse certa de HTMLElement, sem instanciar nenhuma delas.
  • React.createElementO JSX compila para uma chamada de fábrica: você descreve o que quer, e o React decide o que construir.
  • Array.fromUm único ponto de criação que aceita fontes muito diferentes e devolve sempre um array.

O padrão em uma linha

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

// Subclasse decide qual produto criar.
criarBotao(): Botao { return new BotaoMac(); }

O que ele cobra

1 salto a mais entre a chamada e o efeito.

  • Uma classe e uma hierarquia a mais só para adiar a escolha do tipo concreto
  • Cada produto novo tende a exigir uma subclasse de fábrica correspondente

Não vale se: só existe um tipo concreto e nada indica que vão surgir outros — aí new direto é mais honesto que a cerimônia.

Costuma ser confundido com

Onde isto aparece

Continue por aqui