Strategy Pattern in Go: Implementing Flexible Algorithms

Explore the Strategy Pattern in Go, learn how to define interchangeable algorithms, and see practical examples with payment processing systems.

2.3.9 Strategy

The Strategy Pattern is a behavioral design pattern that enables selecting an algorithm’s behavior at runtime. It defines a family of algorithms, encapsulates each one, and makes them interchangeable. This pattern allows the algorithm to vary independently from clients that use it, promoting flexibility and reusability.

Purpose of the Strategy Pattern

  • Define a Family of Algorithms: The Strategy Pattern allows you to define a family of algorithms, encapsulate each one, and make them interchangeable. This is particularly useful when you have multiple ways of performing an operation.
  • Independent Variation: It lets the algorithm vary independently from clients that use it, which means you can add new algorithms without changing the client code.
  • Eliminate Conditional Statements: By using the Strategy Pattern, you can eliminate complex conditional statements for selecting behaviors, leading to cleaner and more maintainable code.

Implementation Steps

1. Strategy Interface

The first step in implementing the Strategy Pattern is to define a strategy interface. This interface declares a method that all concrete strategies will implement.

1// PaymentStrategy defines the interface for payment strategies
2type PaymentStrategy interface {
3    Pay(amount float64) string
4}

2. Concrete Strategies

Next, implement the strategy interface with specific algorithms. Each concrete strategy encapsulates a different algorithm.

 1// CreditCardStrategy is a concrete strategy for credit card payments
 2type CreditCardStrategy struct{}
 3
 4func (c *CreditCardStrategy) Pay(amount float64) string {
 5    return fmt.Sprintf("Paid %.2f using Credit Card", amount)
 6}
 7
 8// PayPalStrategy is a concrete strategy for PayPal payments
 9type PayPalStrategy struct{}
10
11func (p *PayPalStrategy) Pay(amount float64) string {
12    return fmt.Sprintf("Paid %.2f using PayPal", amount)
13}
14
15// CryptoStrategy is a concrete strategy for cryptocurrency payments
16type CryptoStrategy struct{}
17
18func (c *CryptoStrategy) Pay(amount float64) string {
19    return fmt.Sprintf("Paid %.2f using Cryptocurrency", amount)
20}

3. Context

The context holds a reference to a strategy and allows the strategy to be changed at runtime. It delegates the algorithm execution to the strategy object.

 1// PaymentContext is the context that uses a PaymentStrategy
 2type PaymentContext struct {
 3    strategy PaymentStrategy
 4}
 5
 6// SetStrategy allows changing the strategy at runtime
 7func (p *PaymentContext) SetStrategy(strategy PaymentStrategy) {
 8    p.strategy = strategy
 9}
10
11// ExecutePayment executes the payment using the current strategy
12func (p *PaymentContext) ExecutePayment(amount float64) string {
13    return p.strategy.Pay(amount)
14}

When to Use

  • Multiple Ways of Performing an Operation: Use the Strategy Pattern when you have multiple ways of performing an operation and want to switch between them easily.
  • Eliminate Conditional Statements: It is particularly useful for eliminating complex conditional statements that select different behaviors.

Go-Specific Tips

  • Function Types as Strategies: In Go, you can use function types or first-class functions as strategies for simplicity. This approach can reduce boilerplate code.
  • Stateless Strategies: Ensure strategies are stateless or manage state appropriately to avoid unexpected behavior in concurrent environments.

Example: Payment Processing System

Let’s look at a practical example of a payment processing system that supports different payment strategies such as credit card, PayPal, and cryptocurrency.

 1package main
 2
 3import (
 4    "fmt"
 5)
 6
 7// PaymentStrategy defines the interface for payment strategies
 8type PaymentStrategy interface {
 9    Pay(amount float64) string
10}
11
12// CreditCardStrategy is a concrete strategy for credit card payments
13type CreditCardStrategy struct{}
14
15func (c *CreditCardStrategy) Pay(amount float64) string {
16    return fmt.Sprintf("Paid %.2f using Credit Card", amount)
17}
18
19// PayPalStrategy is a concrete strategy for PayPal payments
20type PayPalStrategy struct{}
21
22func (p *PayPalStrategy) Pay(amount float64) string {
23    return fmt.Sprintf("Paid %.2f using PayPal", amount)
24}
25
26// CryptoStrategy is a concrete strategy for cryptocurrency payments
27type CryptoStrategy struct{}
28
29func (c *CryptoStrategy) Pay(amount float64) string {
30    return fmt.Sprintf("Paid %.2f using Cryptocurrency", amount)
31}
32
33// PaymentContext is the context that uses a PaymentStrategy
34type PaymentContext struct {
35    strategy PaymentStrategy
36}
37
38// SetStrategy allows changing the strategy at runtime
39func (p *PaymentContext) SetStrategy(strategy PaymentStrategy) {
40    p.strategy = strategy
41}
42
43// ExecutePayment executes the payment using the current strategy
44func (p *PaymentContext) ExecutePayment(amount float64) string {
45    return p.strategy.Pay(amount)
46}
47
48func main() {
49    context := &PaymentContext{}
50
51    // Use Credit Card Strategy
52    context.SetStrategy(&CreditCardStrategy{})
53    fmt.Println(context.ExecutePayment(100.0))
54
55    // Switch to PayPal Strategy
56    context.SetStrategy(&PayPalStrategy{})
57    fmt.Println(context.ExecutePayment(200.0))
58
59    // Switch to Cryptocurrency Strategy
60    context.SetStrategy(&CryptoStrategy{})
61    fmt.Println(context.ExecutePayment(300.0))
62}

Advantages and Disadvantages

Advantages:

  • Flexibility: Easily switch between different algorithms at runtime.
  • Scalability: Add new strategies without modifying existing code.
  • Maintainability: Reduce complex conditional logic, improving code readability.

Disadvantages:

  • Overhead: Introduces additional classes or functions, which may increase complexity.
  • Increased Complexity: May lead to a higher number of objects in the system.

Best Practices

  • Use Interfaces Wisely: Define clear and concise interfaces for strategies to ensure flexibility.
  • Keep Strategies Stateless: Design strategies to be stateless or manage state carefully to avoid concurrency issues.
  • Leverage Function Types: Consider using function types for strategies in Go to simplify the implementation.

Comparisons

The Strategy Pattern is often compared with the State Pattern. While both involve changing behavior, the Strategy Pattern is used for interchangeable algorithms, whereas the State Pattern is used for changing behavior based on an object’s state.

Conclusion

The Strategy Pattern is a powerful tool for designing flexible and maintainable software. By encapsulating algorithms and making them interchangeable, you can create systems that are easy to extend and modify. In Go, leveraging interfaces and function types can simplify the implementation of this pattern, making it a valuable addition to your design toolkit.

Quiz Time!

Loading quiz…
Revised on Thursday, April 23, 2026