factory pattern

- Published on
- /8 mins read/
Hãy mở mã nguồn của dự án bạn đang làm việc: Bạn có bắt gặp những class
PaymentFactoryhayNotificationFactorychứa một khốiswitch-casehoặc chuỗiif-else ifdài 200 dòng? Mỗi khi có một cổng thanh toán mới (VNPay, MoMo, ZaloPay, Stripe) gia nhập hệ thống, một lập trình viên lại nhảy vào sửa class Factory đó, tiềm ẩn nguy cơ làm vỡ các case cũ. Đó là một nghịch lý kiến trúc cay đắng: Factory Pattern sinh ra để tuân thủ nguyên tắc Open/Closed (Mở để mở rộng, Đóng để sửa đổi), nhưng cách cài đặt nghiệp dư lại biến nó thành kẻ thù lớn nhất của OCP!
Trong các hệ thống Enterprise quy mô lớn, việc tạo đối tượng không thể dừng lại ở những cấu trúc rẽ nhánh tĩnh. Chúng ta cần những Dynamic Factory Patterns có khả năng tự động phát hiện (Auto-Discovery), tự động đăng ký (Self-Registration) và mở rộng không giới hạn mà không cần chạm vào một dòng mã cũ.
Bài viết này phân tích sự tiến hóa từ GoF Factory cổ điển sang Spring-Driven Dynamic Factory và kiến trúc Functional Factory trong Java hiện đại.
# nghịch lý kiến trúc của Factory kinh điển (the ocp violation trap)
Hãy nhìn vào cách triển khai Factory mà hầu hết các sách giáo khoa hay hướng dẫn nhập môn thường dạy:
// CÁI BẪY ANTI-PATTERN: Vi phạm nghiêm trọng Open/Closed Principle!
public class HardcodedPaymentFactory {
public PaymentGateway createGateway(PaymentType type) {
return switch (type) {
case VNPAY -> new VnPayGateway();
case MOMO -> new MoMoGateway();
case STRIPE -> new StripeGateway();
// Thêm cổng thanh toán mới -> BẮT BUỘC PHẢI SỬA CLASS NÀY!
default -> throw new UnsupportedOperationException("Không hỗ trợ");
};
}
}# tại sao đây là một thiết kế tồi?
- Vi phạm nguyên tắc OCP: Bạn không thể mở rộng hệ thống mà không sửa đổi mã nguồn đã chạy ổn định.
- Khóa chặt việc khởi tạo: Nếu
VnPayGatewaycần inject các dependency phức tạp (HttpClient,AuditService,SecretKeyProvider), Factory class sẽ biến thành một "bãi rác phụ thuộc" (Dependency Dumpster).
# giải pháp enterprise: Spring-driven dynamic Factory (map injection)
Trong hệ sinh thái Spring Boot, chúng ta có thể loại bỏ hoàn toàn khối switch-case bằng cách tận dụng cơ chế Auto-Wiring Collection / Map của Spring IoC Container.
# triển khai chuẩn mực
# định nghĩa interface với định danh chiến lược
public interface PaymentGateway {
PaymentResult process(PaymentRequest request);
PaymentType getSupportedType(); // Trả về Enum hoặc String định danh
}# hiện thực hóa các gateway độc lập
@Component("VNPAY")
public class VnPayGateway implements PaymentGateway {
@Override
public PaymentResult process(PaymentRequest request) { ... }
@Override
public PaymentType getSupportedType() { return PaymentType.VNPAY; }
}
@Component("MOMO")
public class MoMoGateway implements PaymentGateway {
@Override
public PaymentResult process(PaymentRequest request) { ... }
@Override
public PaymentType getSupportedType() { return PaymentType.MOMO; }
}# dynamic Factory tự động liên kết (zero switch-case!)
@Service
public class EnterprisePaymentFactory {
private final Map<PaymentType, PaymentGateway> gatewayMap;
// Spring tự động thu gom toàn bộ các Bean implements PaymentGateway và truyền vào List
public EnterprisePaymentFactory(List<PaymentGateway> gateways) {
this.gatewayMap = gateways.stream()
.collect(Collectors.toUnmodifiableMap(
PaymentGateway::getSupportedType,
Function.identity()
));
}
public PaymentGateway getGateway(PaymentType type) {
PaymentGateway gateway = gatewayMap.get(type);
if (gateway == null) {
throw new UnsupportedPaymentException("Không tìm thấy bộ xử lý cho cổng: " + type);
}
return gateway;
}
}Sức mạnh tuyệt đối của OCP: Khi doanh nghiệp muốn tích hợp thêm
ZaloPay, lập trình viên chỉ cần tạo một fileZaloPayGateway.javamới gắn@Component("ZALOPAY"). Hệ thống tự động phát hiện và nạp vào Factory khi khởi động. Không một file mã nguồn cũ nào bị chỉnh sửa!
# functional Factory: tối ưu với Java Supplier (không dùng Spring)
Nếu bạn đang viết một thư viện thuần Java (Core SDK) không có Spring Framework, làm thế nào để xây dựng Factory vừa linh hoạt, vừa không tốn chi phí khởi tạo trước?
Sử dụng Map<K, Supplier<V>> kết hợp Method References:
public class FunctionalDocumentFactory {
private final Map<String, Supplier<DocumentParser>> registry = new ConcurrentHashMap<>();
public FunctionalDocumentFactory() {
// Đăng ký lazy constructor thông qua Method Reference
register("PDF", PdfDocumentParser::new);
register("DOCX", DocxDocumentParser::new);
register("EXCEL", ExcelDocumentParser::new);
}
public void register(String format, Supplier<DocumentParser> supplier) {
registry.put(format.toUpperCase(), Objects.requireNonNull(supplier));
}
public DocumentParser createParser(String format) {
Supplier<DocumentParser> supplier = registry.get(format.toUpperCase());
if (supplier == null) {
throw new IllegalArgumentException("Định dạng tài liệu không hỗ trợ: " + format);
}
return supplier.get(); // Tạo instance mới chỉ khi được yêu cầu!
}
}# phân biệt sâu sắc: Factory method vs Abstract Factory
Rất nhiều kỹ sư nhầm lẫn giữa Factory Method và Abstract Factory. Dưới đây là ranh giới phân định dưới góc độ kiến trúc:
# khi nào dùng Abstract Factory trong kiến trúc multi-cloud?
Khi hệ thống của bạn cần chạy trên cả AWS và Google Cloud:
- Bạn không muốn mã nghiệp vụ phụ thuộc vào AWS S3 hay Google Cloud Storage.
- Bạn định nghĩa
CloudFactorycung cấp:createBlobStorage(),createMessageQueue(),createDatabase(). - Khi deploy trên AWS, nạp
AwsCloudFactory→ Đảm bảo toàn bộ Blob, Queue, DB đều thuộc hệ sinh thái AWS đồng bộ, không bị lẫn lộn giữa S3 của AWS với Pub/Sub của GCP!
# bảng so sánh các biến thể Factory
| Mẫu Hình | Cơ Chế Hoạt Động | Độ Tuân Thủ OCP | Độ Phức Tạp | Khi Nào Sử Dụng? |
|---|---|---|---|---|
| Simple Factory (Switch-Case) | Một class duy nhất chứa logic rẽ nhánh | ❌ Rất kém | Thấp | Ứng dụng đồ chơi, số lượng type cố định vĩnh viễn (vd: thứ trong tuần). |
| Spring Dynamic Factory | Tự động gom Bean qua List<T> hoặc Map<String, T> | 🟢 Tuyệt đối | Thấp (nhờ Spring) | Chuẩn mực bắt buộc cho 95% dự án Spring Boot Enterprise. |
| Functional Factory (Supplier) | Đăng ký lambda qua Registry Map | 🟢 Tuyệt đối | Trung bình | Các core libraries, SDK độc lập không phụ thuộc Spring framework. |
| Abstract Factory (GoF) | Tạo một họ các sản phẩm phụ thuộc lẫn nhau | 🟢 Rất cao | Cao | Hệ thống Multi-Cloud, Cross-Platform GUI, Đa công cụ DB. |
# bảng kiểm tra sẵn sàng vận hành (architect's checklist)
- 1. Loại Bỏ Switch-Case Cứng: Quét toàn bộ mã nguồn kiểm tra các class Factory; thay thế các chuỗi
if-else / switchbằng Spring Map Injection hoặc Functional Registry. - 2. Định Danh Duy Nhất Cho Component: Đảm bảo mỗi Bean triển khai interface cung cấp một identifier duy nhất và bất biến.
- 3. Xử Lý Trường Hợp Không Tìm Thấy (Fallback): Factory luôn ném một custom business exception có ngữ cảnh rõ ràng (ví dụ:
UnsupportedProviderException) thay vì âm thầm trả vềnull. - 4. Hỗ Trợ Đăng Ký Động Khi Runtime: Nếu hệ thống có tính năng Plugin động (Dynamic Plugin Loading), Registry Map phải sử dụng
ConcurrentHashMapđể bảo đảm thread-safety khi đăng ký thêm module. - 5. Giám Sát Metric: Bổ sung metric đo lường tần suất triệu hồi của từng Provider (ví dụ:
payment.gateway.invocations{provider="VNPAY"}) để theo dõi lưu lượng thực tế.
# lời kết
Factory Pattern khi được thiết kế đúng cách không phải là một mớ bòng bong của các câu lệnh điều kiện. Nó là nghệ thuật trừu tượng hóa quá trình khởi tạo đối tượng, trao quyền mở rộng tối đa cho hệ thống mà vẫn giữ vững tính đóng băng bất khả xâm phạm của mã nguồn hiện hữu.
Làm chủ Dynamic Factory trong Spring Boot chính là bước chuyển hóa tư duy từ một lập trình viên viết code rập khuôn sang một Kiến trúc sư Phần mềm có tầm nhìn dài hạn cho sự phát triển của hệ thống.
Chỉ là những ghi chép cá nhân với hy vọng mang lại chút giá trị. Nếu thấy hữu ích, đừng ngại chia sẻ cho bạn bè & đồng nghiệp nhé!
Happy coding 😎 👍🏻 🚀 🔥.
On this page
- # nghịch lý kiến trúc của Factory kinh điển (the ocp violation trap)
- # tại sao đây là một thiết kế tồi?
- # giải pháp enterprise: Spring-driven dynamic Factory (map injection)
- # triển khai chuẩn mực
- # định nghĩa interface với định danh chiến lược
- # hiện thực hóa các gateway độc lập
- # dynamic Factory tự động liên kết (zero switch-case!)
- # functional Factory: tối ưu với Java Supplier (không dùng Spring)
- # phân biệt sâu sắc: Factory method vs Abstract Factory
- # khi nào dùng Abstract Factory trong kiến trúc multi-cloud?
- # bảng so sánh các biến thể Factory
- # bảng kiểm tra sẵn sàng vận hành (architect's checklist)
- # lời kết