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 dePaymentMethodque 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 quePaymentProcessor, ou mude o método parapublicse 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
Buttonmas não temCheckboxjunto, 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
Buttonda factory A eCheckboxda 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.