Factory Method và Abstract Factory trong Java

1. Tổng quan

Factory Method và Abstract Factory đều thuộc nhóm creational pattern, đều giúp bạn tạo object mà không cần new trực tiếp class cụ thể. Nhưng hai pattern này giải quyết bài toán khác nhau và rất dễ nhầm lẫn khi mới học.

Trong bài này, bạn sẽ thấy sự khác biệt qua ví dụ PaymentMethod (Factory Method) và hệ thống tạo UI theo theme (Abstract Factory). Đọc xong, bạn sẽ biết khi nào chỉ cần một factory đơn giản, khi nào cần cả một “nhà máy sản xuất nhà máy”.

2. Factory Method là gì?

Factory Method định nghĩa một method để tạo object, nhưng để subclass quyết định class cụ thể nào sẽ được tạo ra. Nói đơn giản: bạn có một interface method create(), mỗi subclass override nó để trả về một implementation khác nhau.

2.1. Vấn đề cần giải quyết

Giả sử bạn có hệ thống thanh toán với nhiều loại: CreditCardPayment, MomoPayment, BankTransferPayment. Nếu code client tự new trực tiếp:

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

Mỗi lần thêm loại thanh toán mới, bạn phải sửa if-else này ở mọi nơi dùng nó. Đây chính là mùi code (code smell) mà Factory Method giải quyết.

2.2. Áp dụng Factory Method

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

public class CreditCardPayment implements PaymentMethod {
    @Override
    public void pay(BigDecimal amount) {
        System.out.println("Thanh toán " + amount + " bằng thẻ tín dụng");
    }
}

public class MomoPayment implements PaymentMethod {
    @Override
    public void pay(BigDecimal amount) {
        System.out.println("Thanh toán " + amount + " qua Momo");
    }
}

Giải thích: PaymentMethod là interface chung, mỗi implementation chỉ lo logic thanh toán của riêng mình. Đây là “sản phẩm” (product) mà factory sẽ tạo ra.

Tiếp theo là abstract class chứa Factory Method:

public abstract class PaymentProcessor {

    // Factory Method - subclass sẽ quyết định tạo instance nào
    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() là Factory Method: nó là method trừu tượng, mỗi subclass override để trả về đúng loại PaymentMethod mình cần.
  • processOrder() là “template” dùng chung, không cần biết chi tiết loại thanh toán nào đang được tạo.

Client dùng như sau:

PaymentProcessor processor = new MomoProcessor();
processor.processOrder(new Order(new BigDecimal("150000")));
Thanh toán 150000 qua Momo

2.3. Test Factory Method

class PaymentProcessorTest {

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

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

Lưu ý: Để test gọi được createPaymentMethod() (protected), đặt test class cùng package với PaymentProcessor, hoặc đổi method sang public nếu cần test từ bên ngoài.

3. Abstract Factory là gì?

Abstract Factory cung cấp một interface để tạo ra một họ (family) các object liên quan, mà không cần chỉ rõ class cụ thể của từng object. Khác với Factory Method chỉ tạo một loại sản phẩm, Abstract Factory tạo nhiều sản phẩm phải “đi cùng nhau”.

3.1. Vấn đề cần giải quyết

Giả sử bạn làm UI theme cho ứng dụng: theme “Light” và theme “Dark”. Mỗi theme cần có Button và Checkbox riêng, và hai thứ này phải đồng bộ style với nhau — không thể trộn LightButton với DarkCheckbox.

public interface Button {
    void render();
}

public interface Checkbox {
    void render();
}

public class LightButton implements Button {
    @Override
    public void render() {
        System.out.println("Render button nền trắng, chữ đen");
    }
}

public class DarkButton implements Button {
    @Override
    public void render() {
        System.out.println("Render button nền đen, chữ trắng");
    }
}

public class LightCheckbox implements Checkbox {
    @Override
    public void render() {
        System.out.println("Render checkbox viền xám nhạt");
    }
}

public class DarkCheckbox implements Checkbox {
    @Override
    public void render() {
        System.out.println("Render checkbox viền trắng");
    }
}

3.2. Định nghĩ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 là interface định nghĩa cả họ sản phẩm (Button + Checkbox). Mỗi implementation (LightThemeFactory, DarkThemeFactory) đảm bảo các sản phẩm tạo ra luôn đồng bộ về theme.

3.3. Dùng Abstract Factory trong client

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();
Render button nền đen, chữ trắng
Render checkbox viền trắng

ThemedApplication không biết và không cần biết class cụ thể nào đang được tạo. Nó chỉ phụ thuộc vào interface UiComponentFactory, nên đổi theme chỉ cần đổi factory được inject vào.

3.4. Test 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);
    }
}

Test này khẳng định đúng tính chất quan trọng nhất của Abstract Factory: các sản phẩm tạo ra từ cùng một factory luôn thuộc cùng một “họ”, không bị lẫn Light với Dark.

4. So sánh Factory Method và Abstract Factory

Tiêu chí Factory Method Abstract Factory
Số loại sản phẩm 1 loại sản phẩm (PaymentMethod) Nhiều loại sản phẩm liên quan (Button, Checkbox)
Cơ chế Override một method trong subclass Compose nhiều factory method vào một interface
Quan hệ Inheritance (kế thừa) Thường dùng composition (inject factory)
Mục tiêu Giải phóng client khỏi việc biết class cụ thể của một object Đảm bảo tính nhất quán giữa nhiều object liên quan

Một cách nhớ đơn giản: Abstract Factory thường được triển khai bằng một tập hợp các Factory Method. Trong ví dụ trên, createButton() và createCheckbox() trong UiComponentFactory chính là hai Factory Method con.

4.1. Khi nào dùng Factory Method

  • Bạn chỉ cần tạo một loại object, nhưng logic tạo object phức tạp hoặc thay đổi theo ngữ cảnh.
  • Bạn đang làm việc với class hierarchy và muốn subclass tự quyết định loại object con sẽ dùng (template method pattern thường đi kèm pattern này).

4.2. Khi nào dùng Abstract Factory

  • Bạn cần tạo nhiều object liên quan và phải đảm bảo chúng tương thích với nhau (cùng theme, cùng database vendor, cùng platform…).
  • Hệ thống có khả năng mở rộng thêm “họ sản phẩm” mới (ví dụ thêm HighContrastThemeFactory) mà không muốn sửa code client.

Lưu ý: Đừng dùng Abstract Factory khi bạn chỉ có một loại sản phẩm — nó sẽ tạo ra một layer interface dư thừa. Factory Method đơn giản là đủ.

5. Tích hợp với Spring Boot

Trong Spring, bạn ít khi viết tay Factory Method/Abstract Factory theo đúng cấu trúc GoF, vì Spring container đã đóng vai trò factory. Tuy nhiên hiểu rõ ý tưởng giúp bạn thiết kế bean tốt hơn.

<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("Không hỗ trợ loại thanh toán: " + type);
        }
        method.pay(amount);
    }
}

Ở đây, Spring container đóng vai trò “nhà máy” thật sự: nó tạo sẵn các bean PaymentMethod và client chỉ lookup theo key. Đây là biến thể thực dụng của Factory Method trong môi trường Spring — bạn không cần viết class XxxProcessor riêng cho từng loại thanh toán nữa.

6. Lỗi thường gặp

  • Nhầm Factory Method với static factory method (như Optional.of(), List.of()). Static factory method chỉ là một cách tạo object gọn hơn constructor, không liên quan đến polymorphism của GoF pattern.
  • Lạm dụng Abstract Factory khi chỉ có một sản phẩm: nếu bạn chỉ có Button mà không có Checkbox đi kèm, Abstract Factory là over-engineering.
  • Để factory biết quá nhiều logic nghiệp vụ: factory chỉ nên lo việc tạo object, không nên nhúng logic xử lý đơn hàng, validate, v.v. vào trong đó.
  • Quên đảm bảo tính nhất quán trong Abstract Factory: nếu bạn cho phép client tự chọn Button từ factory A và Checkbox từ factory B, bạn đã phá vỡ mục đích chính của pattern.

7. Kết luận

Factory Method phù hợp khi bạn cần delegate việc tạo một object cho subclass, giúp code mở rộng dễ dàng theo nguyên lý Open/Closed. Abstract Factory đi xa hơn: nó đảm bảo một họ object liên quan luôn được tạo đồng bộ, tránh tình trạng trộn lẫn các thành phần không tương thích.

Trong thực tế với Spring Boot, bạn thường không viết thủ công theo cấu trúc GoF mà tận dụng DI container làm factory. Nhưng nắm rõ bản chất hai pattern này giúp bạn đọc hiểu code thư viện (ví dụ DocumentBuilderFactory, ConnectionFactory trong JDBC) và thiết kế module mở rộng tốt hơn.

design-patternjavacreational-patternspring-bootclean-code