1. Giới thiệu về Factory Method
Trong lập trình hướng đối tượng, đôi khi bạn không biết chính xác loại đối tượng cần tạo cho đến khi chương trình chạy. Hoặc bạn muốn để quá trình tạo đối tượng linh hoạt hơn, dễ mở rộng hơn thay vì gắn cứng từ đầu bằng toán tử new
.
Lúc này, bạn cần đến Factory Method Pattern – một trong những mẫu thiết kế thuộc nhóm Creational Pattern, giúp tách biệt quá trình khởi tạo đối tượng khỏi phần sử dụng đối tượng đó.
🔧 Định nghĩa (theo GoF)
“Factory Method định nghĩa một interface cho việc tạo đối tượng, nhưng để các lớp con quyết định loại đối tượng sẽ được tạo. Nó cho phép một lớp ủy quyền việc khởi tạo đối tượng cho các lớp con.”
Nói cách khác, Factory Method giúp tạo ra đối tượng một cách linh hoạt mà không cần biết trước lớp cụ thể nào sẽ được khởi tạo.
2. So sánh Factory Method vs Strategy Pattern
Đây là 2 mẫu dễ bị nhầm lẫn, vì đều liên quan đến việc “ẩn chi tiết triển khai thực tế”. Tuy nhiên, về bản chất, chúng phục vụ mục tiêu khác nhau.
Tiêu chí | Factory Method | Strategy |
---|---|---|
Nhóm Pattern | Creational (Tạo đối tượng) | Behavioral (Hành vi) |
Mục đích | Tạo đối tượng phù hợp trong thời gian chạy | Lựa chọn hành vi phù hợp trong thời gian chạy |
Cách dùng | Ẩn việc tạo đối tượng cụ thể bên trong một interface/abstract class | Tách biệt thuật toán thành các lớp khác nhau và hoán đổi được |
Cấu trúc | Chứa interface tạo ra đối tượng | Chứa interface thực thi hành vi |
Tính mở rộng | Dễ mở rộng đối tượng mới | Dễ thêm hành vi mới |
Ví dụ:
- Factory Method: Hệ thống thanh toán tạo ra
PaypalPayment
,VNPayPayment
,ZaloPayPayment
dựa vào input cấu hình. - Strategy: Một lớp
Payment
có thể thay đổi thuật toáncalculateFee()
tùy thuộc vào từng loại phí (chiết khấu, hoa hồng, tỷ giá…).
3. Dấu hiệu nhận biết nên dùng Factory Method
Bạn nên nghĩ đến Factory Method Pattern khi:
Bạn cần khởi tạo các đối tượng mà không muốn ràng buộc mã nguồn vào lớp cụ thể.
Bạn muốn dễ dàng mở rộng, thêm lớp mới mà không sửa mã hiện tại (Open/Closed Principle).
Bạn nhận thấy có nhiều đoạn code if/else
hay switch-case
dùng để khởi tạo đối tượng cụ thể.
Việc tạo đối tượng có thể phức tạp (không chỉ là new
, mà còn cấu hình, ràng buộc…).
Có nhiều loại đối tượng cùng interface, nhưng ứng dụng chỉ cần dùng interface chung.
4. Ví dụ Factory Method bằng PHP
Bài toán: Tạo hệ thống thanh toán đa nền tảng
Bước 1: Tạo interface chung
interface PaymentGateway {
public function charge($amount);
}
Bước 2: Tạo các class cụ thể
class PaypalPayment implements PaymentGateway {
public function charge($amount) {
echo "Thanh toán $amount VNĐ qua PayPal.\n";
}
}
class VNPayPayment implements PaymentGateway {
public function charge($amount) {
echo "Thanh toán $amount VNĐ qua VNPay.\n";
}
}
class ZaloPayPayment implements PaymentGateway {
public function charge($amount) {
echo "Thanh toán $amount VNĐ qua ZaloPay.\n";
}
}
Bước 3: Tạo lớp Factory
abstract class PaymentFactory {
abstract public function createPayment(): PaymentGateway;
}
Bước 4: Các factory cụ thể
class PaypalFactory extends PaymentFactory {
public function createPayment(): PaymentGateway {
return new PaypalPayment();
}
}
class VNPayFactory extends PaymentFactory {
public function createPayment(): PaymentGateway {
return new VNPayPayment();
}
}
class ZaloPayFactory extends PaymentFactory {
public function createPayment(): PaymentGateway {
return new ZaloPayPayment();
}
}
Bước 5: Client code
function processPayment(PaymentFactory $factory, $amount) {
$payment = $factory->createPayment();
$payment->charge($amount);
}
// Gọi thử
processPayment(new PaypalFactory(), 100000);
processPayment(new VNPayFactory(), 200000);
processPayment(new ZaloPayFactory(), 300000);
Kết quả:
Thanh toán 100000 VNĐ qua PayPal.
Thanh toán 200000 VNĐ qua VNPay.
Thanh toán 300000 VNĐ qua ZaloPay.
Ưu điểm: Client code không cần biết chính xác class nào được tạo ra. Nó chỉ làm việc với
PaymentGateway
– interface chung.
5. Bài toán ứng dụng Factory Method
🔎 Bài toán: Gửi thông báo qua nhiều kênh khác nhau (SMS, Email, Zalo)
Bạn có thể dùng Factory Method để trừu tượng hóa quá trình tạo đối tượng gửi thông báo.
Mục tiêu:
- Gửi message mà không cần biết dùng kênh nào.
- Dễ dàng thêm kênh mới trong tương lai.
Thiết kế:
- Interface
Notifier
có hàmsend($message)
. - Các class:
EmailNotifier
,SmsNotifier
,ZaloNotifier
implement từNotifier
. - Factory
NotifierFactory
sẽ tạo notifier phù hợp dựa trên cấu hình.
interface Notifier {
public function send(string $message);
}
class EmailNotifier implements Notifier {
public function send(string $message) {
echo "Gửi email: $message\n";
}
}
class SmsNotifier implements Notifier {
public function send(string $message) {
echo "Gửi SMS: $message\n";
}
}
class ZaloNotifier implements Notifier {
public function send(string $message) {
echo "Gửi Zalo: $message\n";
}
}
abstract class NotifierFactory {
abstract public function createNotifier(): Notifier;
}
class EmailFactory extends NotifierFactory {
public function createNotifier(): Notifier {
return new EmailNotifier();
}
}
class SmsFactory extends NotifierFactory {
public function createNotifier(): Notifier {
return new SmsNotifier();
}
}
class ZaloFactory extends NotifierFactory {
public function createNotifier(): Notifier {
return new ZaloNotifier();
}
}
// Client
function notify(NotifierFactory $factory, string $message) {
$notifier = $factory->createNotifier();
$notifier->send($message);
}
// Gửi thử
notify(new SmsFactory(), 'Bạn có đơn hàng mới!');
notify(new ZaloFactory(), 'Đơn hàng của bạn đã giao thành công!');
6. Ưu điểm & Nhược điểm của Factory Method
Ưu điểm
- Tuân thủ nguyên tắc SOLID: đặc biệt là Open/Closed và Single Responsibility.
- Dễ mở rộng, thêm loại đối tượng mới mà không sửa mã hiện có.
- Giảm sự phụ thuộc vào lớp cụ thể.
- Tăng khả năng test (mock dễ dàng).
Nhược điểm
- Gây phức tạp cho hệ thống nếu dùng quá sớm hoặc quá mức.
- Tạo nhiều lớp nhỏ chỉ để sinh đối tượng.
- Khó đọc đối với những người chưa quen mẫu thiết kế.