Factory Method y Abstract Factory en Java

1. Descripción general

Factory Method y Abstract Factory pertenecen al grupo de patrones de creación, ambos te ayudan a crear objetos sin necesidad de hacer new directamente a una clase específica. Pero estos dos patrones resuelven problemas diferentes y es muy fácil confundirlos cuando eres principiante.

En este artículo, verás la diferencia a través del ejemplo PaymentMethod (Factory Method) y un sistema de creación de UI según el tema (Abstract Factory). Después de leerlo, sabrás cuándo solo necesitas un factory simple y cuándo necesitas toda una “fábrica que produce fábricas”.

2. ¿Qué es Factory Method?

Factory Method define un método para crear objetos, pero permite que la subclase decida qué clase específica será instanciada. Dicho de forma simple: tienes un método en una interface create(), cada subclase lo reemplaza para retornar una implementación diferente.

2.1. El problema a resolver

Supongamos que tienes un sistema de pagos con muchos tipos: CreditCardPayment, MomoPayment, BankTransferPayment. Si el código del cliente hace new directamente:

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());
}

Cada vez que añadas un tipo de pago nuevo, tendrás que modificar este if-else en todas partes donde se use. Este es precisamente el olor a código (code smell) que Factory Method resuelve.

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("Pagar " + amount + " con tarjeta de crédito");
    }
}

public class MomoPayment implements PaymentMethod {
    @Override
    public void pay(BigDecimal amount) {
        System.out.println("Pagar " + amount + " a través de Momo");
    }
}

Explicación: PaymentMethod es una interface común, cada implementación solo se encarga de la lógica de pago de sí misma. Este es el “producto” (product) que el factory creará.

A continuación, una clase abstracta que contiene Factory Method:

public abstract class PaymentProcessor {

    // Factory Method - la subclase decidirá qué instancia crear
    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() es Factory Method: es un método abstracto, cada subclase lo reemplaza para retornar el tipo correcto de PaymentMethod que necesita.
  • processOrder() es una “plantilla” que se reutiliza, no necesita saber el detalle de qué tipo de pago se está creando.

El cliente la usa así:

PaymentProcessor processor = new MomoProcessor();
processor.processOrder(new Order(new BigDecimal("150000")));
Pagar 150000 a través de Momo

2.3. Testeando Factory Method

class PaymentProcessorTest {

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

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

Nota: Para que el test pueda llamar a createPaymentMethod() (protected), coloca la clase de test en el mismo package que PaymentProcessor, o cambia el método a public si necesitas testearlo desde afuera.

3. ¿Qué es Abstract Factory?

Abstract Factory proporciona una interface para crear una familia (family) de objetos relacionados, sin necesidad de especificar la clase exacta de cada objeto. A diferencia de Factory Method que solo crea un tipo de producto, Abstract Factory crea múltiples productos que deben “ir juntos”.

3.1. El problema a resolver

Supongamos que estás haciendo temas de UI para una aplicación: tema “Claro” y tema “Oscuro”. Cada tema necesita sus propios Button y Checkbox, y estos dos deben estar sincronizados en estilo — no puedes mezclar LightButton con 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ón con fondo blanco, texto negro");
    }
}

public class DarkButton implements Button {
    @Override
    public void render() {
        System.out.println("Renderizar botón con fondo negro, texto blanco");
    }
}

public class LightCheckbox implements Checkbox {
    @Override
    public void render() {
        System.out.println("Renderizar checkbox con borde gris claro");
    }
}

public class DarkCheckbox implements Checkbox {
    @Override
    public void render() {
        System.out.println("Renderizar checkbox con borde blanco");
    }
}

3.2. Definiendo 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 es una interface que define toda la familia de productos (Button + Checkbox). Cada implementación (LightThemeFactory, DarkThemeFactory) garantiza que los productos creados siempre sean consistentes en términos de tema.

3.3. Usando Abstract Factory en el 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ón con fondo negro, texto blanco
Renderizar checkbox con borde blanco

ThemedApplication no sabe ni necesita saber qué clases específicas se están creando. Solo depende de la interface UiComponentFactory, así que cambiar de tema es solo cambiar el factory que se inyecta.

3.4. Testeando 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 test confirma la propiedad más importante de Abstract Factory: los productos creados desde el mismo factory siempre pertenecen a la misma “familia”, sin riesgo de mezclar Light con Dark.

4. Comparativa entre Factory Method y Abstract Factory

Criterio Factory Method Abstract Factory
Número de productos 1 tipo de producto (PaymentMethod) Múltiples tipos de productos relacionados (Button, Checkbox)
Mecanismo Reemplazar un método en la subclase Combinar múltiples factory methods en una interface
Relación Herencia Generalmente composición (inyectar factory)
Objetivo Liberar al cliente de conocer la clase exacta de un objeto Garantizar consistencia entre múltiples objetos relacionados

Una forma simple de recordarlo: Abstract Factory usualmente se implementa usando un conjunto de Factory Methods. En el ejemplo anterior, createButton() y createCheckbox() en UiComponentFactory son dos Factory Methods.

4.1. Cuándo usar Factory Method

  • Solo necesitas crear un tipo de objeto, pero la lógica de creación es compleja o varía según el contexto.
  • Estás trabajando con una jerarquía de clases y quieres que la subclase decida qué tipo de objeto hijo usará (el patrón template method usualmente va de la mano con este).

4.2. Cuándo usar Abstract Factory

  • Necesitas crear múltiples objetos relacionados y debes garantizar que sean compatibles entre sí (mismo tema, mismo proveedor de base de datos, misma plataforma…).
  • El sistema puede extenderse con nuevas “familias de productos” (por ejemplo añadir HighContrastThemeFactory) sin modificar el código del cliente.

Nota: No uses Abstract Factory si solo tienes un tipo de producto — será una layer de interface innecesaria. Factory Method simple es suficiente.

5. Integración con Spring Boot

En Spring, raramente escribes Factory Method/Abstract Factory de forma manual siguiendo la estructura GoF exactamente, porque el contenedor de Spring ya actúa como factory. Sin embargo, entender bien la idea te ayuda a diseñar beans mejor.

<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 pago no soportado: " + type);
        }
        method.pay(amount);
    }
}

Aquí, el contenedor de Spring actúa como una “factory” real: crea anticipadamente los beans PaymentMethod y el cliente solo busca por clave. Esta es una variante práctica de Factory Method en el entorno Spring — no necesitas escribir una clase XxxProcessor separada para cada tipo de pago.

6. Errores comunes

  • Confundir Factory Method con static factory method (como Optional.of(), List.of()). Un static factory method es solo una forma más compacta de crear objetos en lugar de usar el constructor, no tiene relación con el patrón polimórfico de GoF.
  • Abusar de Abstract Factory cuando solo hay un producto: si solo tienes Button pero no Checkbox que lo acompañe, Abstract Factory es sobre-ingeniería.
  • Dejar que factory conozca demasiada lógica de negocio: un factory solo debe preocuparse por crear objetos, no debería incluir lógica de procesamiento de pedidos, validación, etc.
  • Olvidar garantizar consistencia en Abstract Factory: si permites que el cliente elija Button de factory A y Checkbox de factory B, has roto el propósito principal del patrón.

7. Conclusión

Factory Method es adecuado cuando necesitas delegar la creación de un objeto a la subclase, permitiendo que el código se extienda fácilmente según el principio Open/Closed. Abstract Factory va más allá: garantiza que una familia de objetos relacionados siempre se cree de forma consistente, evitando la mezcla de componentes incompatibles.

En la práctica con Spring Boot, usualmente no escribes manualmente la estructura GoF sino que aprovechas el contenedor de DI para actuar como factory. Pero comprender bien la esencia de estos dos patrones te ayuda a leer y entender código de librerías (por ejemplo DocumentBuilderFactory, ConnectionFactory en JDBC) y a diseñar módulos extensibles de forma correcta.

design-patternjavacreational-patternspring-bootclean-code