Abstract Factory
Intenção (GoF)
Seção intitulada “Intenção (GoF)”Fornecer uma interface para criar famílias de objetos relacionados ou dependentes sem especificar suas classes concretas.
Problema que resolve
Seção intitulada “Problema que resolve”Imagine um sistema que gera relatórios em diferentes formatos de saída — HTML e PDF. Cada formato precisa de um conjunto coordenado de peças: um cabeçalho, uma tabela e um rodapé compatíveis entre si. Se o cliente instancia new CabecalhoHtml() e depois new TabelaPdf(), ele pode misturar famílias incompatíveis, e cada ponto de criação fica acoplado às classes concretas.
O Abstract Factory encapsula a criação de toda a família atrás de uma interface. O cliente escolhe uma fábrica concreta uma vez; a partir daí, todos os produtos que ele obtém pertencem à mesma família, coerentemente.
O problema, em código
Seção intitulada “O problema, em código”public class GeradorRelatorio { public String gerar(String formato, List<String> dados) { StringBuilder sb = new StringBuilder(); if (formato.equals("html")) { sb.append(new CabecalhoHtml().render()); sb.append(new TabelaHtml().render(dados)); sb.append(new RodapeHtml().render()); } else if (formato.equals("pdf")) { sb.append(new CabecalhoPdf().render()); sb.append(new TabelaPdf().render(dados)); sb.append(new RodapePdf().render()); } return sb.toString(); }}Cada novo formato exige editar este if/else (viola OCP), e nada impede um new CabecalhoHtml() colado com new TabelaPdf().
Solução com o padrão
Seção intitulada “Solução com o padrão”public interface Cabecalho { String render(); }public interface Tabela { String render(List<String> dados); }public interface Rodape { String render(); }
public interface FabricaRelatorio { Cabecalho criarCabecalho(); Tabela criarTabela(); Rodape criarRodape();}Família HTML:
public class CabecalhoHtml implements Cabecalho { public String render() { return "<h1>Relatório</h1>"; }}public class TabelaHtml implements Tabela { public String render(List<String> dados) { return "<table>" + String.join("", dados.stream() .map(d -> "<tr><td>" + d + "</td></tr>").toList()) + "</table>"; }}public class RodapeHtml implements Rodape { public String render() { return "<footer>fim</footer>"; }}
public class FabricaRelatorioHtml implements FabricaRelatorio { public Cabecalho criarCabecalho() { return new CabecalhoHtml(); } public Tabela criarTabela() { return new TabelaHtml(); } public Rodape criarRodape() { return new RodapeHtml(); }}Família PDF:
public class CabecalhoPdf implements Cabecalho { public String render() { return "[PDF] RELATORIO"; }}public class TabelaPdf implements Tabela { public String render(List<String> dados) { return "[PDF-TABELA] " + String.join(" | ", dados); }}public class RodapePdf implements Rodape { public String render() { return "[PDF] --- fim ---"; }}
public class FabricaRelatorioPdf implements FabricaRelatorio { public Cabecalho criarCabecalho() { return new CabecalhoPdf(); } public Tabela criarTabela() { return new TabelaPdf(); } public Rodape criarRodape() { return new RodapePdf(); }}Cliente desacoplado das classes concretas:
public class GeradorRelatorio {
private final FabricaRelatorio fabrica;
public GeradorRelatorio(FabricaRelatorio fabrica) { this.fabrica = fabrica; }
public String gerar(List<String> dados) { return fabrica.criarCabecalho().render() + fabrica.criarTabela().render(dados) + fabrica.criarRodape().render(); }}var gerador = new GeradorRelatorio(new FabricaRelatorioHtml());String saida = gerador.gerar(List.of("linha 1", "linha 2"));Trocar para PDF é injetar new FabricaRelatorioPdf() — nenhuma mudança no GeradorRelatorio. Adicionar uma família CSV é criar novas classes, sem editar as existentes.
Estrutura
Seção intitulada “Estrutura”classDiagram
class FabricaRelatorio {
<<interface>>
+criarCabecalho() Cabecalho
+criarTabela() Tabela
+criarRodape() Rodape
}
class FabricaRelatorioHtml
class FabricaRelatorioPdf
class Cabecalho {
<<interface>>
}
class Tabela {
<<interface>>
}
class GeradorRelatorio
FabricaRelatorio <|.. FabricaRelatorioHtml
FabricaRelatorio <|.. FabricaRelatorioPdf
GeradorRelatorio --> FabricaRelatorio : depende da abstração
FabricaRelatorio ..> Cabecalho : cria
FabricaRelatorio ..> Tabela : cria
Contraste com Factory Method
Seção intitulada “Contraste com Factory Method”| Aspecto | Factory Method | Abstract Factory |
|---|---|---|
| Mecanismo | Herança: subclasse sobrescreve um método de criação | Composição: cliente recebe um objeto-fábrica |
| Escopo | Cria um produto | Cria uma família de produtos relacionados |
| Variação | Uma dimensão (o produto) | Coerência entre vários produtos da mesma família |
| Relação | Abstract Factory frequentemente é implementada com vários Factory Methods (um por produto) | — |
Regra prática: se você precisa criar um único tipo de objeto com variação, Factory Method basta. Se precisa criar um conjunto coordenado que deve variar em bloco, use Abstract Factory.
Ligação com princípios SOLID
Seção intitulada “Ligação com princípios SOLID”- DIP: o
GeradorRelatoriodepende da abstraçãoFabricaRelatorio, não das classes concretas. A fábrica concreta é injetada de fora — inversão de dependência em ação. - OCP: adicionar uma nova família (CSV, Markdown) é criar novas classes que implementam as interfaces, sem tocar no cliente nem nas famílias existentes. Fechado para modificação, aberto para extensão.
Consequências e trade-offs
Seção intitulada “Consequências e trade-offs”- A favor: isola classes concretas, garante coerência entre produtos da família, facilita a troca de famílias inteiras.
- Contra: adicionar um novo tipo de produto (ex: uma “Marca d’água”) exige alterar a interface da fábrica e todas as fábricas concretas — o padrão é rígido nessa dimensão. É um dos seus custos mais citados.
Anti-padrões e erros comuns
Seção intitulada “Anti-padrões e erros comuns”- Abstract Factory sem família real: se só existe um produto, é Factory Method disfarçado com boilerplate extra.
- Vazamento de concreto: o cliente que faz downcast do produto para a classe concreta anula o desacoplamento.
- Interface de fábrica que cresce sem parar: sinal de que a “família” não é coesa; talvez sejam famílias diferentes.
- Aplicar por antecipação: introduzir o padrão sem uma segunda família concreta prevista é over-engineering.
Relações
Seção intitulada “Relações”- Categoria: Padrões Criacionais.
- Índice: Design Patterns (GoF).
- Padrão criacional relacionado: Builder (constrói um objeto complexo; Abstract Factory cria uma família).
- Princípios: DIP - Dependency Inversion Principle, OCP - Open-Closed Principle.
Perguntas de revisão
Seção intitulada “Perguntas de revisão”1. Qual é a diferença central entre Factory Method e Abstract Factory?
Resposta
Factory Method usa herança para variar a criação de um produto (subclasse sobrescreve o método). Abstract Factory usa composição para criar uma família de produtos relacionados e coerentes entre si. Um Abstract Factory costuma ser implementado com vários Factory Methods internos.
2. Como o padrão realiza DIP e OCP?
Resposta
DIP: o cliente depende da interface da fábrica (abstração), com a fábrica concreta injetada de fora. OCP: novas famílias entram como novas classes implementando as interfaces, sem modificar o cliente nem as famílias existentes.
3. Qual é a principal rigidez/custo do Abstract Factory?
Resposta
Adicionar um novo tipo de produto à família obriga a mudar a interface da fábrica e todas as fábricas concretas. O padrão é flexível para novas famílias, mas rígido para novos produtos dentro da família.
4. Quando aplicar Abstract Factory seria over-engineering?
Resposta
Quando existe apenas uma família ou apenas um produto, sem variação coordenada real prevista. Nesse caso o padrão só adiciona interfaces e classes de fábrica sem resolver nenhum problema concreto — um Factory Method ou até um new direto seria suficiente.