Design Pattern Series - Factory Pattern (Golang)
The Factory Pattern is a design pattern belonging to the Creational Patterns group. It provides a way to create objects without directly specifying a specific class. Instead, the Factory Pattern uses a factory method or factory structure to determine the type of object to create based on input parameters or conditions.
In Go, because inheritance is not supported like traditional object-oriented languages, the Factory Pattern is often implemented using functions or structs, combined with interfaces to ensure flexibility and abstraction.
Purpose
- Object creation abstraction: Hides the object creation logic, making code easier to maintain and extend.
- Increased flexibility: Allows adding new object types without modifying existing code (Open/Closed Principle).
- Centralized control: Object creation logic is in one place, easy to manage and optimize.
- Reduced dependencies: Client-side code only works with interfaces or abstract types, without needing to know specific implementation details.
Use cases of Factory Pattern?
- When you need to create multiple objects that share the same interface.
- When the object creation logic is complex (e.g. initializing with default configuration or special values).
- When you want to separate the object creation logic from the object usage logic.
Example
- Database Connection: Create different connection (MySQL, PostgreSQL, SQLite) based on config.
- Payment Processing: Manage multiple payment methods (CreditCard, PayPal) in the e-commerce system.
Implement Factory Pattern in Go
Let's say we have a payment system that supports two types: CreditCard and PayPal. Each type has a common method Pay. We will use the Factory Pattern to create an appropriate object based on the payment type requested.
Step 1: Declare Interface
type PaymentMethod interface {
Pay(amount float64) string
}All payment methods must implement the method Pay.
Step 2: Create struct for each method
type CreditCardPayment struct{}
func (c *CreditCardPayment) Pay(amount float64) string {
return fmt.Sprintf("Paid %.2f using Credit Card", amount)
}
type PayPalPayment struct{}
func (p *PayPalPayment) Pay(amount float64) string {
return fmt.Sprintf("Paid %.2f using PayPal", amount)
}Step 3: Crete factory
func PaymentMethodFactory(paymentType string) (PaymentMethod, error) {
switch paymentType {
case "creditcard":
return &CreditCardPayment{}, nil
case "paypal":
return &PayPalPayment{}, nil
default:
return nil, fmt.Errorf("unsupported payment method: %s", paymentType)
}
}Step 4: Use factory
func main() {
// Credit Card
creditCard, err := PaymentMethodFactory("creditcard")
if err != nil {
log.Fatal(err)
}
fmt.Println(creditCard.Pay(100.50))
// PayPal
payPal, err := PaymentMethodFactory("paypal")
if err != nil {
log.Fatal(err)
}
fmt.Println(payPal.Pay(75.25))
// Phương thức không hỗ trợ
_, err = PaymentMethodFactory("bitcoin")
if err != nil {
fmt.Println(err)
}
}Explain example
- Step 1: Interface
PaymentMethodhelps abstraction, the client only needs to work with the interface instead of the concrete implementation. - Step 2: Structs
CreditCardPayment,PayPalPaymentimplement methodPay. - Step 3:
PaymentMethodFactorycentralizes all object creation logic. - Step 4: Client simplifies the call factory, regardless of how object is created
Full code
package main
import (
"fmt"
"log"
)
// Declare interface
type PaymentMethod interface {
Pay(amount float64) string
}
// Implement CreditCardPayment
type CreditCardPayment struct{}
func (c *CreditCardPayment) Pay(amount float64) string {
return fmt.Sprintf("Paid %.2f using Credit Card", amount)
}
// Implement PayPalPayment
type PayPalPayment struct{}
func (p *PayPalPayment) Pay(amount float64) string {
return fmt.Sprintf("Paid %.2f using PayPal", amount)
}
// Factory function
func PaymentMethodFactory(paymentType string) (PaymentMethod, error) {
switch paymentType {
case "creditcard":
return &CreditCardPayment{}, nil
case "paypal":
return &PayPalPayment{}, nil
default:
return nil, fmt.Errorf("unsupported payment method: %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)
}
}Output
Paid 100.50 using Credit Card
Paid 75.25 using PayPal
unsupported payment method: bitcoinConclude
Factory Pattern is a powerful tool for managing object creation in Go, especially when you need to support multiple object types that share a common interface. It makes code extensible, maintainable and follows object-oriented design principles.
This example can be extended by:
- Add new payment methods (BankTransfer, Crypto).
- Add more complex initialization logic in the factory.
- Use config or parameters to customize the created object.
Next post in series Abstract Factory Pattern