Design Pattern Series - Factory Pattern (Golang)

Programing May 25, 2025

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 PaymentMethod helps abstraction, the client only needs to work with the interface instead of the concrete implementation.
  • Step 2: Structs CreditCardPayment, PayPalPayment implement method Pay.
  • Step 3: PaymentMethodFactory centralizes 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: bitcoin

Conclude

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

Tags