Pular para o conteúdo

Abstract Factory

Fornecer uma interface para criar famílias de objetos relacionados ou dependentes sem especificar suas classes concretas.

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.

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().

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.

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
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.

  • DIP: o GeradorRelatorio depende da abstração FabricaRelatorio, 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.
  • 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.
  • 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.

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.