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
ifem 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:
-
AbstractFactory (interface): declara os métodos de criação
para cada tipo de produto da família. Ex.:
criarBotao()ecriarCheckbox(). -
ConcreteFactory: implementa a AbstractFactory criando
produtos de uma variante específica. Ex.:
WindowsFactorycriaWindowsBotaoeWindowsCheckbox. -
AbstractProduct (interface por tipo): define o contrato
de cada tipo de produto. Ex.:
Botaocomrenderizar(),Checkboxcommarcar(). -
ConcreteProduct: implementa o AbstractProduct para uma
variante. Ex.:
WindowsBotao,MacOsBotao. - 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
<?php
// ── AbstractProducts ─────────────────────────────────────────
interface Botao
{
public function renderizar(): string;
}
interface Checkbox
{
public function marcar(): string;
}
// ── ConcreteProducts — tema Light ────────────────────────────
class LightBotao implements Botao
{
public function renderizar(): string
{
return '[LIGHT] Botao branco renderizado';
}
}
class LightCheckbox implements Checkbox
{
public function marcar(): string
{
return '[LIGHT] Checkbox claro marcado';
}
}
// ── ConcreteProducts — tema Dark ─────────────────────────────
class DarkBotao implements Botao
{
public function renderizar(): string
{
return '[DARK] Botao escuro renderizado';
}
}
class DarkCheckbox implements Checkbox
{
public function marcar(): string
{
return '[DARK] Checkbox escuro marcado';
}
}
// ── AbstractFactory ───────────────────────────────────────────
interface UIFactory
{
public function criarBotao(): Botao;
public function criarCheckbox(): Checkbox;
}
// ── ConcreteFactories ─────────────────────────────────────────
class LightFactory implements UIFactory
{
public function criarBotao(): Botao { return new LightBotao(); }
public function criarCheckbox(): Checkbox { return new LightCheckbox(); }
}
class DarkFactory implements UIFactory
{
public function criarBotao(): Botao { return new DarkBotao(); }
public function criarCheckbox(): Checkbox { return new DarkCheckbox(); }
}
// ── Cliente ───────────────────────────────────────────────────
function renderizarApp(UIFactory $factory): void
{
$botao = $factory->criarBotao();
$checkbox = $factory->criarCheckbox();
echo $botao->renderizar() . PHP_EOL;
echo $checkbox->marcar() . PHP_EOL;
}
// Seleção da família ocorre na borda da aplicação, não no cliente.
$tema = 'dark';
$factory = $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
<?php
// ── AbstractProducts ─────────────────────────────────────────
interface Conexao
{
public function conectar(string $dsn): void;
public function fechar(): void;
}
interface QueryBuilder
{
public function selecionar(string $tabela): string;
}
// ── ConcreteProducts — PostgreSQL ─────────────────────────────
class PgConexao implements Conexao
{
public function conectar(string $dsn): void { echo "[PG] Conectado: {$dsn}" . PHP_EOL; }
public function fechar(): void { echo "[PG] Conexao fechada" . PHP_EOL; }
}
class PgQueryBuilder implements QueryBuilder
{
public function selecionar(string $tabela): string
{
return "SELECT * FROM \"{$tabela}\";"; // → SELECT * FROM "usuarios";
}
}
// ── ConcreteProducts — SQLite (testes) ────────────────────────
class SqliteConexao implements Conexao
{
public function conectar(string $dsn): void { echo "[SQLite] Conectado: {$dsn}" . PHP_EOL; }
public function fechar(): void { echo "[SQLite] Conexao fechada" . PHP_EOL; }
}
class SqliteQueryBuilder implements QueryBuilder
{
public function selecionar(string $tabela): string
{
return "SELECT * FROM `{$tabela}`;"; // → SELECT * FROM `usuarios`;
}
}
// ── AbstractFactory ───────────────────────────────────────────
interface DatabaseFactory
{
public function criarConexao(): Conexao;
public function criarQueryBuilder(): QueryBuilder;
}
// ── ConcreteFactories ─────────────────────────────────────────
class PostgresFactory implements DatabaseFactory
{
public function criarConexao(): Conexao { return new PgConexao(); }
public function criarQueryBuilder(): QueryBuilder { return new PgQueryBuilder(); }
}
class SqliteFactory implements DatabaseFactory
{
public function criarConexao(): Conexao { return new SqliteConexao(); }
public function criarQueryBuilder(): QueryBuilder { return new SqliteQueryBuilder(); }
}
// ── Cliente ───────────────────────────────────────────────────
function executarConsulta(DatabaseFactory $factory, string $dsn): void
{
$conn = $factory->criarConexao();
$qb = $factory->criarQueryBuilder();
$conn->conectar($dsn);
echo $qb->selecionar('usuarios') . PHP_EOL;
$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.