Design Pattern Series - Factory Pattern (Golang)
Factory Pattern là một mẫu thiết kế (design pattern) thuộc nhóm Creational Patterns (Nhóm khởi tạo). Nó cung cấp một cách để tạo ra các đối tượng mà không cần chỉ định trực tiếp lớp (class) cụ thể nào sẽ được khởi tạo. Thay vào đó, Factory Pattern sử dụng một phương thức hoặc cấu trúc "nhà máy" để quyết định loại đối tượng cần tạo dựa trên các tham số đầu vào hoặc điều kiện cụ thể.
Trong Go, vì ngôn ngữ này không hỗ trợ kế thừa theo cách truyền thống như các ngôn ngữ hướng đối tượng khác, Factory Pattern thường được triển khai bằng các hàm (functions) hoặc struct, kết hợp với interface để đảm bảo tính linh hoạt và tính trừu tượng.
Mục đích của Factory Pattern
- Trừu tượng hóa việc khởi tạo: Che giấu logic tạo đối tượng phức tạp, giúp mã nguồn dễ bảo trì và mở rộng hơn.
- Tăng tính linh hoạt: Cho phép thêm các loại đối tượng mới mà không cần sửa đổi mã nguồn hiện có (Tuân thủ nguyên tắc Open/Closed Principle).
- Kiểm soát tập trung: Logic khởi tạo đối tượng nằm ở một nơi duy nhất, dễ dàng quản lý và tối ưu hóa.
- Giảm thiểu sự phụ thuộc (Loose Coupling): Mã nguồn phía Client chỉ làm việc với các interface hoặc kiểu trừu tượng, không cần biết chi tiết triển khai cụ thể bên trong.
Khi nào nên sử dụng Factory Pattern?
- Khi bạn cần tạo nhiều đối tượng có chung một interface.
- Khi logic tạo đối tượng phức tạp (ví dụ: cần khởi tạo với cấu hình mặc định hoặc các giá trị đặc biệt).
- Khi bạn muốn tách biệt logic tạo đối tượng ra khỏi logic sử dụng đối tượng đó.
Ví dụ thực tế:
- Kết nối cơ sở dữ liệu: Tạo các kết nối khác nhau (MySQL, PostgreSQL, SQLite) dựa trên file cấu hình.
- Xử lý thanh toán: Quản lý nhiều phương thức thanh toán (Credit Card, PayPal, MoMo) trong hệ thống thương mại điện tử.
Triển khai Factory Pattern trong Go
Giả sử chúng ta có một hệ thống thanh toán hỗ trợ hai loại: CreditCard và PayPal. Mỗi loại đều có một phương thức chung là Pay. Chúng ta sẽ sử dụng Factory Pattern để tạo đối tượng phù hợp dựa trên loại thanh toán được yêu cầu.
Bước 1: Khai báo Interface
type PaymentMethod interface {
Pay(amount float64) string
}
Tất cả các phương thức thanh toán phải triển khai phương thức Pay.
Bước 2: Tạo Struct cho từng phương thức thanh toán
type CreditCardPayment struct{}
func (c *CreditCardPayment) Pay(amount float64) string {
return fmt.Sprintf("Đã thanh toán %.2f bằng Credit Card", amount)
}
type PayPalPayment struct{}
func (p *PayPalPayment) Pay(amount float64) string {
return fmt.Sprintf("Đã thanh toán %.2f bằng PayPal", amount)
}
Bước 3: Tạo Factory
func PaymentMethodFactory(paymentType string) (PaymentMethod, error) {
switch paymentType {
case "creditcard":
return &CreditCardPayment{}, nil
case "paypal":
return &PayPalPayment{}, nil
default:
return nil, fmt.Errorf("phương thức thanh toán không hỗ trợ: %s", paymentType)
}
}
Bước 4: Sử dụng Factory trong hàm main
func main() {
// Thanh toán qua Credit Card
creditCard, err := PaymentMethodFactory("creditcard")
if err != nil {
log.Fatal(err)
}
fmt.Println(creditCard.Pay(100.50))
// Thanh toán qua PayPal
payPal, err := PaymentMethodFactory("paypal")
if err != nil {
log.Fatal(err)
}
fmt.Println(payPal.Pay(75.25))
// Trường hợp phương thức không hỗ trợ
_, err = PaymentMethodFactory("bitcoin")
if err != nil {
fmt.Println(err)
}
}
Giải thích ví dụ
- Interface
PaymentMethod: Giúp trừu tượng hóa, Client chỉ cần tương tác với interface thay vì các triển khai cụ thể (concrete implementation). - Struct
CreditCardPaymentvàPayPalPayment: Triển khai phương thứcPaytheo cách riêng của chúng. - Hàm
PaymentMethodFactory: Tập trung tất cả logic khởi tạo đối tượng vào một chỗ. - Client: Đơn giản hóa việc gọi factory, không cần quan tâm đối tượng được tạo ra như thế nào.
Mã nguồn đầy đủ (Full Code)
package main
import (
"fmt"
"log"
)
// Khai báo interface
type PaymentMethod interface {
Pay(amount float64) string
}
// Triển khai CreditCardPayment
type CreditCardPayment struct{}
func (c *CreditCardPayment) Pay(amount float64) string {
return fmt.Sprintf("Đã thanh toán %.2f bằng Credit Card", amount)
}
// Triển khai PayPalPayment
type PayPalPayment struct{}
func (p *PayPalPayment) Pay(amount float64) string {
return fmt.Sprintf("Đã thanh toán %.2f bằng PayPal", amount)
}
// Hàm Factory
func PaymentMethodFactory(paymentType string) (PaymentMethod, error) {
switch paymentType {
case "creditcard":
return &CreditCardPayment{}, nil
case "paypal":
return &PayPalPayment{}, nil
default:
return nil, fmt.Errorf("phương thức thanh toán không hỗ trợ: %s", paymentType)
}
}
func main() {
creditCard, err := PaymentMethodFactory("creditcard")
if err != nil {
log.Fatal(err)
}
fmt.Println(creditCard.Pay(100.50))
payPal, err := PaymentMethodFactory("paypal")
if err != nil {
log.Fatal(err)
}
fmt.Println(payPal.Pay(75.25))
_, err = PaymentMethodFactory("bitcoin")
if err != nil {
fmt.Println(err)
}
}
Kết quả (Output):
Plaintext
Đã thanh toán 100.50 bằng Credit Card
Đã thanh toán 75.25 bằng PayPal
phương thức thanh toán không hỗ trợ: bitcoin
Kết luận
Factory Pattern là một công cụ mạnh mẽ để quản lý việc tạo đối tượng trong Go, đặc biệt khi bạn cần hỗ trợ nhiều loại đối tượng dùng chung một interface. Nó giúp mã nguồn dễ mở rộng, dễ bảo trì và tuân thủ tốt các nguyên tắc thiết kế hướng đối tượng.
Bạn có thể mở rộng ví dụ này bằng cách:
- Thêm các phương thức thanh toán mới (BankTransfer, Crypto).
- Thêm logic khởi tạo phức tạp hơn bên trong factory.
- Sử dụng cấu hình từ file bên ngoài để tùy chỉnh đối tượng được tạo ra.
Cảm ơn bạn đã đọc bài viết! Hy vọng kiến thức này sẽ giúp ích cho quá trình lập trình Go của bạn.
Next post in the series Abstract Factory Pattern