Factory Method e Abstract Factory em Java

1. Visão Geral

Factory Method e Abstract Factory fazem parte do grupo creational pattern, ambos ajudam você a criar objetos sem usar new diretamente em classes específicas. Mas esses dois padrões resolvem problemas diferentes e é muito fácil confundi-los quando está aprendendo.

Neste artigo, você verá a diferença através do exemplo PaymentMethod (Factory Method) e um sistema de criação de UI por tema (Abstract Factory). Após ler, você saberá quando apenas uma factory simples é necessária e quando você precisa de toda uma “fábrica de fábricas”.

2. Factory Method é o quê?

Factory Method define um método para criar objetos, mas deixa para a subclass decidir qual classe específica será criada. Simplificando: você tem um método interface create(), cada subclass o sobrescreve para retornar uma implementação diferente.

2.1. O problema a resolver

Suponha que você tem um sistema de pagamento com vários tipos: CreditCardPayment, MomoPayment, BankTransferPayment. Se o código cliente criar diretamente:

public void processOrder(Order order, String type) {
    PaymentMethod payment;
    if (type.equals("CREDIT_CARD")) {
        payment = new CreditCardPayment();
    } else if (type.equals("MOMO")) {
        payment = new MomoPayment();
    } else {
        payment = new BankTransferPayment();
    }
    payment.pay(order.getAmount());
}

Toda vez que você adiciona um novo tipo de pagamento, precisa modificar esse if-else em todos os lugares onde é usado. Esse é o code smell que Factory Method resolve.

2.2. Aplicando Factory Method

public interface PaymentMethod {
    void pay(BigDecimal amount);
}

public class CreditCardPayment implements PaymentMethod {
    @Override
    public void pay(BigDecimal amount) {
        System.out.println("Pagando " + amount + " com cartão de crédito");
    }
}

public class MomoPayment implements PaymentMethod {
    @Override
    public void pay(BigDecimal amount) {
        System.out.println("Pagando " + amount + " via Momo");
    }
}

Explicação: PaymentMethod é a interface comum, cada implementação só cuida da lógica de pagamento dela. Esse é o “produto” que a factory criará.

Em seguida, vem a classe abstrata contendo o Factory Method:

public abstract class PaymentProcessor {

    // Factory Method - a subclass decidirá qual instância criar
    protected abstract PaymentMethod createPaymentMethod();

    public void processOrder(Order order) {
        PaymentMethod payment = createPaymentMethod();
        payment.pay(order.getAmount());
    }
}

public class CreditCardProcessor extends PaymentProcessor {
    @Override
    protected PaymentMethod createPaymentMethod() {
        return new CreditCardPayment();
    }
}

public class MomoProcessor extends PaymentProcessor {
    @Override
    protected PaymentMethod createPaymentMethod() {
        return new MomoPayment();
    }
}
  • createPaymentMethod() é o Factory Method: é um método abstrato, cada subclass o sobrescreve para retornar o tipo correto de PaymentMethod que precisa.
  • processOrder() é um “template” compartilhado, não precisa saber qual tipo de pagamento está sendo criado.

O cliente usa assim:

PaymentProcessor processor = new MomoProcessor();
processor.processOrder(new Order(new BigDecimal("150000")));
Pagando 150000 via Momo

2.3. Testando Factory Method

class PaymentProcessorTest {

    @Test
    void shouldCreateCorrectPaymentMethod() {
        PaymentProcessor processor = new CreditCardProcessor();
        PaymentMethod method = processor.createPaymentMethod();

        assertThat(method).isInstanceOf(CreditCardPayment.class);
    }
}

Nota: Para que o teste consiga chamar createPaymentMethod() (protected), coloque a classe de teste no mesmo pacote que PaymentProcessor, ou mude o método para public se precisar testar de fora.

3. Abstract Factory é o quê?

Abstract Factory fornece uma interface para criar uma família (family) de objetos relacionados, sem precisar especificar a classe concreta de cada objeto. Diferente de Factory Method que cria apenas um tipo de produto, Abstract Factory cria vários produtos que devem “andar juntos”.

3.1. O problema a resolver

Suponha que você desenvolve temas de UI para uma aplicação: tema “Light” e tema “Dark”. Cada tema precisa ter seu próprio Button e Checkbox, e esses dois devem estar em sincronia de estilo — não é possível misturar LightButton com DarkCheckbox.

public interface Button {
    void render();
}

public interface Checkbox {
    void render();
}

public class LightButton implements Button {
    @Override
    public void render() {
        System.out.println("Renderizar botão com fundo branco, texto preto");
    }
}

public class DarkButton implements Button {
    @Override
    public void render() {
        System.out.println("Renderizar botão com fundo preto, texto branco");
    }
}

public class LightCheckbox implements Checkbox {
    @Override
    public void render() {
        System.out.println("Renderizar checkbox com borda cinza claro");
    }
}

public class DarkCheckbox implements Checkbox {
    @Override
    public void render() {
        System.out.println("Renderizar checkbox com borda branca");
    }
}

3.2. Definindo a Abstract Factory

public interface UiComponentFactory {
    Button createButton();
    Checkbox createCheckbox();
}

public class LightThemeFactory implements UiComponentFactory {
    @Override
    public Button createButton() {
        return new LightButton();
    }

    @Override
    public Checkbox createCheckbox() {
        return new LightCheckbox();
    }
}

public class DarkThemeFactory implements UiComponentFactory {
    @Override
    public Button createButton() {
        return new DarkButton();
    }

    @Override
    public Checkbox createCheckbox() {
        return new DarkCheckbox();
    }
}

UiComponentFactory é a interface que define toda a família de produtos (Button + Checkbox). Cada implementação (LightThemeFactory, DarkThemeFactory) garante que os produtos criados sempre estarão em sincronia com o tema.

3.3. Usando Abstract Factory no cliente

public class ThemedApplication {

    private final UiComponentFactory factory;

    public ThemedApplication(UiComponentFactory factory) {
        this.factory = factory;
    }

    public void renderUi() {
        Button button = factory.createButton();
        Checkbox checkbox = factory.createCheckbox();
        button.render();
        checkbox.render();
    }
}
ThemedApplication app = new ThemedApplication(new DarkThemeFactory());
app.renderUi();
Renderizar botão com fundo preto, texto branco
Renderizar checkbox com borda branca

ThemedApplication não sabe e não precisa saber qual classe específica está sendo criada. Ela apenas depende da interface UiComponentFactory, então trocar de tema é só trocar a factory injetada.

3.4. Testando Abstract Factory

class ThemedApplicationTest {

    @Test
    void shouldCreateConsistentComponentsForDarkTheme() {
        UiComponentFactory factory = new DarkThemeFactory();

        Button button = factory.createButton();
        Checkbox checkbox = factory.createCheckbox();

        assertThat(button).isInstanceOf(DarkButton.class);
        assertThat(checkbox).isInstanceOf(DarkCheckbox.class);
    }
}

Este teste afirma a propriedade mais importante da Abstract Factory: os produtos criados pela mesma factory sempre pertencem à mesma “família”, sem risco de misturar Light com Dark.

4. Comparando Factory Method e Abstract Factory

Critério Factory Method Abstract Factory
Número de tipos de produto 1 tipo de produto (PaymentMethod) Múltiplos tipos de produtos relacionados (Button, Checkbox)
Mecanismo Sobrescrever um método na subclass Compor vários factory methods em uma interface
Relação Herança (inheritance) Geralmente usa composition (injetar factory)
Objetivo Liberar o cliente de conhecer a classe concreta de um objeto Garantir consistência entre múltiplos objetos relacionados

Uma forma fácil de lembrar: Abstract Factory geralmente é implementada usando um conjunto de Factory Methods. No exemplo acima, createButton() e createCheckbox() em UiComponentFactory são dois Factory Methods.

4.1. Quando usar Factory Method

  • Você precisa criar apenas um tipo de objeto, mas a lógica de criação é complexa ou muda conforme o contexto.
  • Você está trabalhando com uma class hierarchy e quer que a subclass decida qual tipo de objeto ela usará (template method pattern frequentemente vai com esse padrão).

4.2. Quando usar Abstract Factory

  • Você precisa criar múltiplos objetos relacionados e deve garantir que sejam compatíveis entre si (mesmo tema, mesmo database vendor, mesma plataforma…).
  • O sistema tem capacidade de expandir adicionando uma nova “família de produtos” (por exemplo, adicionar HighContrastThemeFactory) sem querer modificar o código do cliente.

Nota: Não use Abstract Factory quando você tem apenas um tipo de produto — isso criará uma camada de interface redundante. Factory Method simples é o suficiente.

5. Integrando com Spring Boot

Em Spring, você raramente escreve manualmente Factory Method/Abstract Factory seguindo a estrutura GoF correta, porque o Spring container já faz o papel de factory. No entanto, entender bem a ideia ajuda você a projetar beans melhor.

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter</artifactId>
</dependency>
@Configuration
public class PaymentFactoryConfig {

    @Bean
    public Map<String, PaymentMethod> paymentMethods() {
        return Map.of(
            "CREDIT_CARD", new CreditCardPayment(),
            "MOMO", new MomoPayment()
        );
    }
}
@Service
public class PaymentService {

    private final Map<String, PaymentMethod> paymentMethods;

    public PaymentService(Map<String, PaymentMethod> paymentMethods) {
        this.paymentMethods = paymentMethods;
    }

    public void pay(String type, BigDecimal amount) {
        PaymentMethod method = paymentMethods.get(type);
        if (method == null) {
            throw new IllegalArgumentException("Tipo de pagamento não suportado: " + type);
        }
        method.pay(amount);
    }
}

Aqui, o Spring container faz o papel de “fábrica” real: ele cria antecipadamente os beans PaymentMethod e o cliente apenas faz lookup pela chave. Essa é uma variante pragmática do Factory Method em ambiente Spring — você não precisa escrever a classe XxxProcessor separadamente para cada tipo de pagamento.

6. Erros comuns

  • Confundir Factory Method com static factory method (como Optional.of(), List.of()). Static factory method é apenas uma forma mais concisa de criar um objeto do que o construtor, não tem relação com o padrão GoF polimórfico.
  • Exagerar com Abstract Factory quando você tem apenas um produto: se você só tem Button mas não tem Checkbox junto, Abstract Factory é over-engineering.
  • Deixar a factory saber lógica de negócio demais: a factory deve apenas se preocupar com criar objetos, não deve embutir lógica de processamento de pedidos, validação, etc. dentro dela.
  • Esquecer de garantir consistência em Abstract Factory: se você permitir que o cliente escolha Button da factory A e Checkbox da factory B, você quebrou o propósito principal do padrão.

7. Conclusão

Factory Method é apropriado quando você precisa delegar a criação de um objeto para a subclass, ajudando a expandir o código facilmente seguindo o princípio Open/Closed. Abstract Factory vai além: ela garante que uma família de objetos relacionados seja sempre criada de forma sincronizada, evitando a situação de misturar componentes incompatíveis.

Na prática com Spring Boot, você geralmente não escreve manualmente seguindo a estrutura GoF mas aproveita o DI container como factory. Porém, dominar a essência desses dois padrões ajuda você a entender código de bibliotecas (como DocumentBuilderFactory, ConnectionFactory em JDBC) e projetar módulos extensíveis melhor.

design-patternjavacreational-patternspring-bootclean-code