Padrão de Criação (GoF)

Abstract Factory

Fornece uma interface para criar famílias de objetos relacionados sem especificar suas classes concretas — garantindo que os produtos de uma mesma família sejam sempre compatíveis entre si.

Intenção

Definir uma interface (a fábrica abstrata) que declara métodos de criação para cada produto de uma família. Cada implementação concreta dessa interface — a fábrica concreta — cria uma variante coesa dos produtos. O código cliente trabalha exclusivamente com as interfaces abstratas: da fábrica e dos produtos. Nunca referencia as classes concretas diretamente.

Catalogado por Gamma, Helm, Johnson e Vlissides no livro Design Patterns: Elements of Reusable Object-Oriented Software (1994), o Abstract Factory pertence à categoria de padrões de criação. É frequentemente descrito como um "super-factory" ou "factory de factories", mas a essência é mais simples: coordenar a criação de múltiplos produtos que precisam trabalhar juntos.

Problema

Imagine um toolkit de componentes de interface gráfica que deve funcionar em dois sistemas operacionais: Windows e macOS. Cada SO tem sua própria aparência e comportamento para botões, checkboxes, menus e janelas. O problema central não é criar um componente isolado — é garantir que todos os componentes usados juntos pertençam à mesma família visual. Misturar um botão Windows com um checkbox macOS resulta numa interface inconsistente.

A solução ingênua — um if/switch espalhado pelo código cliente escolhendo qual classe concreta instanciar — cria dois problemas sérios:

  • Acoplamento ao SO: o código de negócio precisa saber se está rodando em Windows ou macOS, misturando decisão de plataforma com lógica de apresentação.
  • Inconsistência garantida: basta um desenvolvedor esquecer um if em algum lugar para misturar componentes de famílias diferentes. O compilador não detecta esse erro.

Abstract Factory vs Factory Method

A distinção mais importante do padrão — e a que mais gera confusão:

  • Factory Method cria um único produto. A variação está em qual classe concreta desse produto é instanciada, controlada por subclasses do Creator. É um ponto de extensão para um tipo de objeto.
  • Abstract Factory cria uma família inteira de produtos relacionados. Uma única fábrica concreta é responsável por criar botão, checkbox e menu — todos do mesmo tema/plataforma. É uma coordenação de múltiplos factory methods em torno de uma família coesa.

De fato, as fábricas concretas do Abstract Factory costumam implementar seus métodos de criação usando o padrão Factory Method internamente. O Abstract Factory é, conceitualmente, uma composição de Factory Methods agrupados por família.

Solução

O Abstract Factory organiza o código em cinco participantes:

  1. AbstractFactory (interface): declara os métodos de criação para cada tipo de produto da família. Ex.: criarBotao() e criarCheckbox().
  2. ConcreteFactory: implementa a AbstractFactory criando produtos de uma variante específica. Ex.: WindowsFactory cria WindowsBotao e WindowsCheckbox.
  3. AbstractProduct (interface por tipo): define o contrato de cada tipo de produto. Ex.: Botao com renderizar(), Checkbox com marcar().
  4. ConcreteProduct: implementa o AbstractProduct para uma variante. Ex.: WindowsBotao, MacOsBotao.
  5. Cliente: usa somente as interfaces AbstractFactory e AbstractProduct. Recebe a fábrica concreta via injeção (construtor ou parâmetro) — nunca a instancia diretamente.

Para trocar toda a família (mudar do tema Windows para macOS), basta fornecer uma fábrica concreta diferente ao cliente. Nenhuma linha de código do cliente precisa mudar.

Estrutura

Diagrama UML simplificado com o exemplo de kit de UI por plataforma:

          «interface»               «interface»
            Botao                    Checkbox
    ┌──────────────────┐     ┌──────────────────────┐
    │ + renderizar()   │     │ + marcar()            │
    └──────────────────┘     └──────────────────────┘
            ▲                          ▲
    ┌───────┴───────┐          ┌───────┴────────┐
    │               │          │                │
WindowsBotao   MacOsBotao  WindowsCheckbox  MacOsCheckbox
(ConcreteProduct)           (ConcreteProduct)


          «interface»
        UIFactory
    ┌──────────────────────────┐
    │ + criarBotao(): Botao    │
    │ + criarCheckbox():       │
    │     Checkbox             │
    └──────────────────────────┘
              ▲
    ┌─────────┴────────────────┐
    │                          │
WindowsFactory            MacOsFactory
(ConcreteFactory)         (ConcreteFactory)
criarBotao()              criarBotao()
  → new WindowsBotao()      → new MacOsBotao()
criarCheckbox()           criarCheckbox()
  → new WindowsCheckbox()   → new MacOsCheckbox()


Fluxo de chamada:

  cliente (recebe UIFactory via injeção)
    │
    │  const botao   = factory.criarBotao()
    │  const check   = factory.criarCheckbox()
    │  botao.renderizar()
    │  check.marcar()
    ▼
  Resultado: todos os componentes são da mesma família.
  Para trocar de plataforma: injete uma fábrica diferente.

Exemplos de código

Exemplo 1 — Kit de UI por tema (Light / Dark)

A fábrica abstrata garante que botão e checkbox sejam sempre do mesmo tema. O cliente renderiza a aplicação sem saber qual tema está ativo.

// ── AbstractProducts ─────────────────────────────────────────
interface Botao {
  renderizar(): string;
}

interface Checkbox {
  marcar(): string;
}

// ── ConcreteProducts — tema Light ────────────────────────────
class LightBotao implements Botao {
  renderizar(): string {
    return "[LIGHT] Botao branco renderizado";
  }
}

class LightCheckbox implements Checkbox {
  marcar(): string {
    return "[LIGHT] Checkbox claro marcado";
  }
}

// ── ConcreteProducts — tema Dark ─────────────────────────────
class DarkBotao implements Botao {
  renderizar(): string {
    return "[DARK] Botao escuro renderizado";
  }
}

class DarkCheckbox implements Checkbox {
  marcar(): string {
    return "[DARK] Checkbox escuro marcado";
  }
}

// ── AbstractFactory ───────────────────────────────────────────
interface UIFactory {
  criarBotao(): Botao;
  criarCheckbox(): Checkbox;
}

// ── ConcreteFactories ─────────────────────────────────────────
class LightFactory implements UIFactory {
  criarBotao(): Botao     { return new LightBotao(); }
  criarCheckbox(): Checkbox { return new LightCheckbox(); }
}

class DarkFactory implements UIFactory {
  criarBotao(): Botao     { return new DarkBotao(); }
  criarCheckbox(): Checkbox { return new DarkCheckbox(); }
}

// ── Cliente ───────────────────────────────────────────────────
// Recebe a fábrica via injeção — nunca instancia produtos diretamente.
function renderizarApp(factory: UIFactory): void {
  const botao   = factory.criarBotao();
  const checkbox = factory.criarCheckbox();

  console.log(botao.renderizar());
  console.log(checkbox.marcar());
}

// Seleção da família ocorre na borda da aplicação, não no cliente.
const tema: "light" | "dark" = "dark";
const factory: UIFactory = tema === "dark" ? new DarkFactory() : new LightFactory();

renderizarApp(factory);
// → [DARK] Botao escuro renderizado
// → [DARK] Checkbox escuro marcado

Exemplo 2 — Família de conexões de banco (SQL / NoSQL)

O mesmo princípio aplicado a infraestrutura: a fábrica garante que conexão, query builder e transação sempre pertençam ao mesmo provider. Útil em testes, onde a fábrica concreta aponta para um banco em memória.

// ── AbstractProducts ─────────────────────────────────────────
interface Conexao {
  conectar(dsn: string): void;
  fechar(): void;
}

interface QueryBuilder {
  selecionar(tabela: string): string;
}

// ── ConcreteProducts — PostgreSQL ─────────────────────────────
class PgConexao implements Conexao {
  conectar(dsn: string): void  { console.log(`[PG] Conectado: ${dsn}`); }
  fechar(): void               { console.log("[PG] Conexao fechada"); }
}

class PgQueryBuilder implements QueryBuilder {
  selecionar(tabela: string): string {
    return `SELECT * FROM "${tabela}";`;   // → SELECT * FROM "usuarios";
  }
}

// ── ConcreteProducts — SQLite (testes) ────────────────────────
class SqliteConexao implements Conexao {
  conectar(dsn: string): void  { console.log(`[SQLite] Conectado: ${dsn}`); }
  fechar(): void               { console.log("[SQLite] Conexao fechada"); }
}

class SqliteQueryBuilder implements QueryBuilder {
  selecionar(tabela: string): string {
    return `SELECT * FROM \`${tabela}\`;`;  // → SELECT * FROM `usuarios`;
  }
}

// ── AbstractFactory ───────────────────────────────────────────
interface DatabaseFactory {
  criarConexao(): Conexao;
  criarQueryBuilder(): QueryBuilder;
}

// ── ConcreteFactories ─────────────────────────────────────────
class PostgresFactory implements DatabaseFactory {
  criarConexao(): Conexao         { return new PgConexao(); }
  criarQueryBuilder(): QueryBuilder { return new PgQueryBuilder(); }
}

class SqliteFactory implements DatabaseFactory {
  criarConexao(): Conexao         { return new SqliteConexao(); }
  criarQueryBuilder(): QueryBuilder { return new SqliteQueryBuilder(); }
}

// ── Cliente ───────────────────────────────────────────────────
function executarConsulta(factory: DatabaseFactory, dsn: string): void {
  const conn = factory.criarConexao();
  const qb   = factory.criarQueryBuilder();

  conn.conectar(dsn);
  console.log(qb.selecionar("usuarios"));
  conn.fechar();
}

// Em produção: PostgresFactory. Em testes: SqliteFactory.
executarConsulta(new PostgresFactory(), "postgres://localhost/app");
// → [PG] Conectado: postgres://localhost/app
// → SELECT * FROM "usuarios";
// → [PG] Conexao fechada

Quando usar

  • Quando o sistema deve ser independente de como seus produtos são criados: o cliente não deve saber se está lidando com produtos Windows, macOS, PostgreSQL ou SQLite.
  • Quando os produtos devem ser usados juntos: botão e checkbox do mesmo tema, conexão e query builder do mesmo banco. A fábrica garante a consistência que um conjunto de Factory Methods isolados não garantiria.
  • Quando você quer expor apenas interfaces, não implementações: o Abstract Factory é o mecanismo clássico de bibliotecas que permitem ao consumidor trocar a implementação concreta (ex.: driver de banco de dados).
  • Quando novas famílias serão adicionadas: para adicionar um novo tema ou provider, basta criar uma nova fábrica concreta — sem tocar no código cliente nem nas fábricas existentes.

Quando evitar

  • Quando há apenas uma família e ela nunca vai variar: criar uma AbstractFactory para uma única implementação concreta é overengineering puro. Use Factory Method simples ou instanciação direta.
  • Quando adicionar novos tipos de produto é frequente: o Abstract Factory sofre com extensão de produto (adicionar um terceiro tipo de componente, ex.: Menu): exige alterar a AbstractFactory e todas as fábricas concretas — violando Aberto/Fechado para extensão de tipos de produto.
  • Em projetos com DI containers: frameworks modernos (NestJS, Spring, Laravel) gerenciam famílias de implementações via módulos e escopos de injeção. Reimplementar Abstract Factory manualmente costuma ser redundante.

Prós e contras

Prós

  • Garante compatibilidade entre produtos de uma mesma família — impossibilita misturar produtos de famílias diferentes.
  • Segue o Princípio do Aberto/Fechado para novas famílias: adicionar uma nova fábrica concreta não altera código existente.
  • Segue o Princípio da Responsabilidade Única: cada fábrica concreta cuida da criação de uma família inteira.
  • Desacopla completamente o cliente das classes concretas de produto.
  • Facilita troca de toda a família em tempo de execução (ex.: tema claro/escuro, ambiente de testes vs produção).

Contras

  • Adicionar um novo tipo de produto (ex.: um terceiro componente na família) exige modificar a AbstractFactory e todas as fábricas concretas — o ponto mais frágil do padrão.
  • Introduz uma hierarquia considerável de classes e interfaces mesmo para casos moderados.
  • Pode ser difícil de justificar o custo quando as famílias são poucas e estáveis.

Armadilhas comuns

1. Usar Abstract Factory quando só há uma família (overengineering)

Atenção: Se o sistema tem apenas um conjunto de produtos concretos e nenhuma variação real está planejada, criar uma AbstractFactory com uma única ConcreteFactory adiciona cerimônia sem benefício. Comece com Factory Method ou instanciação direta. Refatore para Abstract Factory quando a segunda família aparecer de verdade.

2. Confundir com Factory Method

Factory Method gerencia a criação de um único tipo de produto via herança de Creator. Abstract Factory gerencia a criação de uma família de tipos via composição de métodos de criação. Se o seu "Abstract Factory" tem apenas um método criar(), provavelmente é um Factory Method disfarçado.

3. Extensão de produto quebra todas as fábricas

Quando um novo tipo de produto precisa ser adicionado à família (ex.: adicionar criarMenu() à interface UIFactory), todas as fábricas concretas existentes precisam ser atualizadas — mesmo aquelas que não usarão o novo produto. Esse é o custo estrutural do padrão. Avalie se a variação principal do seu sistema é de famílias (Abstract Factory é adequado) ou de tipos de produto (Strategy ou Factory Method podem ser melhores).

4. Seleção da fábrica concreta no lugar errado

A escolha de qual fábrica concreta usar deve ocorrer o mais próximo possível da borda da aplicação (ponto de entrada, arquivo de configuração, container de DI). Se o código cliente decide qual fábrica instanciar com um if/switch baseado em configuração, o padrão perde metade do seu valor — o cliente ainda está acoplado às classes concretas das fábricas.

Padrões relacionados

O Abstract Factory se conecta a vários outros padrões de criação:

O Factory Method é o tijolo de construção do Abstract Factory: cada método de criação da fábrica abstrata é, conceitualmente, um Factory Method. O Builder foca no processo de montagem passo a passo de um único objeto complexo, enquanto o Abstract Factory cria famílias inteiras de objetos simples de uma só vez. O Prototype é uma alternativa quando a criação de novos objetos é feita por clonagem de instâncias existentes — pode ser usado dentro de uma fábrica para reduzir o número de subclasses. O Singleton é frequentemente aplicado às fábricas concretas: como a fábrica não tem estado relevante, geralmente só uma instância é necessária.