Strategy Pattern: Flexible Algorithms with Clean Separation

Master the Strategy Pattern to write flexible and interchangeable algorithms. Explore real-world examples, UML, and C#/Python code in this practical guide.

Introduction

The Strategy Pattern is designed to encapsulate a family of algorithms and make them interchangeable at runtime. It enables the algorithm to vary independently from the clients that use it.

What is the Strategy Pattern?

Definition: Defines a family of algorithms, encapsulates each one, and makes them interchangeable.

  • Category: Behavioral
  • Also Known As: Policy Pattern

Real-World Analogy

A navigation app that offers routes for walking, driving, and transit—each a separate strategy, selected at runtime.

UML Diagram

Context → delegates to a Strategy interface, implemented by ConcreteStrategyA, ConcreteStrategyB

+----------------+        +---------------------+
|    Context     |<>----->|      IStrategy      |
|----------------|        |---------------------|
| - strategy     |        | + Execute()         |
|----------------|        +---------------------+
| + SetStrategy()|              
| + Execute()    |              
+----------------+        
         | 
         ↓ 
+------------------+     +------------------+
|ConcreteStrategyA |     |ConcreteStrategyB |
+------------------+     +------------------+
  

Code Example: Payment Strategy


// Strategy Interface
public interface IPaymentStrategy
{
    void Pay(decimal amount);
}

// Concrete Strategy A
public class CreditCardPayment : IPaymentStrategy
{
    public void Pay(decimal amount)
    {
        Console.WriteLine($"Paid {amount:C} using Credit Card.");
    }
}

// Concrete Strategy B
public class PayPalPayment : IPaymentStrategy
{
    public void Pay(decimal amount)
    {
        Console.WriteLine($"Paid {amount:C} using PayPal.");
    }
}

// Context
public class PaymentProcessor
{
    private IPaymentStrategy _paymentStrategy;

    public void SetStrategy(IPaymentStrategy strategy)
    {
        _paymentStrategy = strategy;
    }

    public void ProcessPayment(decimal amount)
    {
        _paymentStrategy.Pay(amount);
    }
}

// Client
public class Program
{
    public static void Main()
    {
        var processor = new PaymentProcessor();

        processor.SetStrategy(new CreditCardPayment());
        processor.ProcessPayment(150.00m);

        processor.SetStrategy(new PayPalPayment());
        processor.ProcessPayment(200.00m);
    }
}
    

from abc import ABC, abstractmethod

class PaymentStrategy(ABC):
    @abstractmethod
    def pay(self, amount):
        pass

class CreditCardPayment(PaymentStrategy):
    def pay(self, amount):
        print(f"Paid ${amount:.2f} using Credit Card.")

class PayPalPayment(PaymentStrategy):
    def pay(self, amount):
        print(f"Paid ${amount:.2f} using PayPal.")

class PaymentProcessor:
    def __init__(self, strategy: PaymentStrategy):
        self._strategy = strategy

    def set_strategy(self, strategy: PaymentStrategy):
        self._strategy = strategy

    def process_payment(self, amount):
        self._strategy.pay(amount)

if __name__ == "__main__":
    processor = PaymentProcessor(CreditCardPayment())
    processor.process_payment(150.00)

    processor.set_strategy(PayPalPayment())
    processor.process_payment(200.00)
    

Code Example: Sorting Strategy


public interface ISortStrategy
{
    void Sort(List list);
}

public class BubbleSort : ISortStrategy
{
    public void Sort(List list)
    {
        for (int i = 0; i < list.Count - 1; i++)
        {
            for (int j = 0; j < list.Count - i - 1; j++)
            {
                if (list[j] > list[j + 1])
                {
                    int temp = list[j];
                    list[j] = list[j + 1];
                    list[j + 1] = temp;
                }
            }
        }
        Console.WriteLine("Sorted using Bubble Sort: " + string.Join(", ", list));
    }
}

public class QuickSort : ISortStrategy
{
    public void Sort(List list)
    {
        list.Sort(); // Using built-in QuickSort
        Console.WriteLine("Sorted using Quick Sort: " + string.Join(", ", list));
    }
}

public class SortContext
{
    private ISortStrategy _sortStrategy;

    public void SetStrategy(ISortStrategy strategy)
    {
        _sortStrategy = strategy;
    }

    public void Sort(List list)
    {
        _sortStrategy.Sort(list);
    }
}

public class SortExample
{
    public static void Main()
    {
        var numbers = new List { 5, 3, 8, 4, 2 };
        var context = new SortContext();

        context.SetStrategy(new BubbleSort());
        context.Sort(new List(numbers));

        context.SetStrategy(new QuickSort());
        context.Sort(new List(numbers));
    }
}
    

from abc import ABC, abstractmethod

class SortStrategy(ABC):
    @abstractmethod
    def sort(self, data):
        pass

class BubbleSort(SortStrategy):
    def sort(self, data):
        arr = data.copy()
        for i in range(len(arr)):
            for j in range(0, len(arr) - i - 1):
                if arr[j] > arr[j + 1]:
                    arr[j], arr[j + 1] = arr[j + 1], arr[j]
        print("Sorted using Bubble Sort:", arr)

class QuickSort(SortStrategy):
    def sort(self, data):
        arr = sorted(data)
        print("Sorted using Quick Sort:", arr)

class SortContext:
    def __init__(self, strategy: SortStrategy):
        self._strategy = strategy

    def set_strategy(self, strategy: SortStrategy):
        self._strategy = strategy

    def sort(self, data):
        self._strategy.sort(data)

if __name__ == "__main__":
    numbers = [5, 3, 8, 4, 2]
    context = SortContext(BubbleSort())
    context.sort(numbers)

    context.set_strategy(QuickSort())
    context.sort(numbers)
    

More Real-World Examples

1. Compression Algorithm

You can use different strategies for compressing files such as ZIP, RAR, or GZIP.

2. Sorting

Context sets sorting strategy: QuickSort, MergeSort, or BubbleSort, depending on performance needs.

3. Authentication Strategy

Login context supports multiple strategies: LDAP, OAuth, SAML, or custom DB auth.

4. Logging Strategy

Log output can be directed to console, file, or cloud service via interchangeable strategies.

When to Use

  • Multiple interchangeable algorithms
  • Eliminating conditionals for algorithm selection
  • Open/Closed Principle compliance

Pros and Cons

ProsCons
Easy to switch strategies Increased number of classes
Adheres to SOLID Client must understand differences

Real-World Use Cases

  • Payment gateways
  • Compression algorithms
  • Input validation
  • Sorting algorithms
  • Authentication methods
  • Logging destinations

Summary

The Strategy Pattern offers a powerful way to delegate and encapsulate behavior, making your code more flexible, extensible, and maintainable.