Skip to content Skip to footer

Strategy Design Pattern: Interchangeable Algorithms

You’re building a payment processing system that needs to support multiple payment methods: credit card, PayPal, bank transfer. Using if/else statements for each method creates tight coupling and makes adding new methods difficult. The Strategy pattern defines a family of algorithms, encapsulates each one, and makes them interchangeable, letting the algorithm vary independently from clients.

Problem

Consider a sorting system:

public class Sorter
{
    public void Sort(int[] array, string algorithm)
    {
        if (algorithm == "BubbleSort")
        {
            // Bubble sort implementation
        }
        else if (algorithm == "QuickSort")
        {
            // Quick sort implementation
        }
        else if (algorithm == "MergeSort")
        {
            // Merge sort implementation
        }
    }
}

This approach has problems:

  • Tight coupling between sorter and algorithms
  • Hard to add new algorithms
  • Violates Open/Closed Principle
  • Algorithm code mixed with client code
  • Difficult to test algorithms independently

Solution

The Strategy pattern defines a family of algorithms, encapsulates each one, and makes them interchangeable. Strategy lets the algorithm vary independently from clients that use it.

Definition: The Strategy pattern lets you define a family of algorithms, put each of them into a separate class, and make their objects interchangeable.

Instead of implementing algorithms directly, you create strategy classes. The context uses a strategy object to perform the algorithm, allowing strategies to be swapped at runtime.

Pseudocode

Context (PaymentProcessor)
    ↓ uses
Strategy Interface (IPaymentStrategy)
    ↓ implemented by
Concrete Strategies (CreditCardStrategy, PayPalStrategy, BankTransferStrategy)

Examples

Here’s a complete, runnable C# implementation:

using System;

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

// Concrete strategies
public class CreditCardStrategy : IPaymentStrategy
{
    private string _cardNumber;
    private string _cvv;

    public CreditCardStrategy(string cardNumber, string cvv)
    {
        _cardNumber = cardNumber;
        _cvv = cvv;
    }

    public void ProcessPayment(decimal amount)
    {
        Console.WriteLine($"Processing ${amount} payment via Credit Card");
        Console.WriteLine($"Card: {_cardNumber}, CVV: {_cvv}");
    }
}

public class PayPalStrategy : IPaymentStrategy
{
    private string _email;

    public PayPalStrategy(string email)
    {
        _email = email;
    }

    public void ProcessPayment(decimal amount)
    {
        Console.WriteLine($"Processing ${amount} payment via PayPal");
        Console.WriteLine($"Email: {_email}");
    }
}

public class BankTransferStrategy : IPaymentStrategy
{
    private string _accountNumber;

    public BankTransferStrategy(string accountNumber)
    {
        _accountNumber = accountNumber;
    }

    public void ProcessPayment(decimal amount)
    {
        Console.WriteLine($"Processing ${amount} payment via Bank Transfer");
        Console.WriteLine($"Account: {_accountNumber}");
    }
}

// Context
public class PaymentProcessor
{
    private IPaymentStrategy _strategy;

    public PaymentProcessor(IPaymentStrategy strategy)
    {
        _strategy = strategy;
    }

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

    public void ProcessPayment(decimal amount)
    {
        _strategy.ProcessPayment(amount);
    }
}

// Usage
class Program
{
    static void Main()
    {
        var processor = new PaymentProcessor(new CreditCardStrategy("1234-5678", "123"));
        processor.ProcessPayment(100m);

        processor.SetStrategy(new PayPalStrategy("user@example.com"));
        processor.ProcessPayment(200m);

        processor.SetStrategy(new BankTransferStrategy("ACC-12345"));
        processor.ProcessPayment(300m);
    }
}

Output:

Processing $100 payment via Credit Card
Card: 1234-5678, CVV: 123
Processing $200 payment via PayPal
Email: user@example.com
Processing $300 payment via Bank Transfer
Account: ACC-12345

Real-World Example: Sorting Algorithms

// Strategy interface
public interface ISortStrategy
{
    void Sort(int[] array);
}

// Concrete strategies
public class BubbleSortStrategy : ISortStrategy
{
    public void Sort(int[] array)
    {
        Console.WriteLine("Sorting using Bubble Sort");
        int n = array.Length;
        for (int i = 0; i < n - 1; i++)
        {
            for (int j = 0; j < n - i - 1; j++)
            {
                if (array[j] > array[j + 1])
                {
                    int temp = array[j];
                    array[j] = array[j + 1];
                    array[j + 1] = temp;
                }
            }
        }
    }
}

public class QuickSortStrategy : ISortStrategy
{
    public void Sort(int[] array)
    {
        Console.WriteLine("Sorting using Quick Sort");
        QuickSort(array, 0, array.Length - 1);
    }

    private void QuickSort(int[] array, int low, int high)
    {
        if (low < high)
        {
            int pi = Partition(array, low, high);
            QuickSort(array, low, pi - 1);
            QuickSort(array, pi + 1, high);
        }
    }

    private int Partition(int[] array, int low, int high)
    {
        int pivot = array[high];
        int i = low - 1;

        for (int j = low; j < high; j++)
        {
            if (array[j] < pivot)
            {
                i++;
                int temp = array[i];
                array[i] = array[j];
                array[j] = temp;
            }
        }

        int temp2 = array[i + 1];
        array[i + 1] = array[high];
        array[high] = temp2;

        return i + 1;
    }
}

// Context
public class Sorter
{
    private ISortStrategy _strategy;

    public Sorter(ISortStrategy strategy)
    {
        _strategy = strategy;
    }

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

    public void Sort(int[] array)
    {
        _strategy.Sort(array);
    }
}

// Usage
var array1 = new int[] { 64, 34, 25, 12, 22, 11, 90 };
var sorter = new Sorter(new BubbleSortStrategy());
sorter.Sort(array1);
Console.WriteLine(string.Join(", ", array1));

var array2 = new int[] { 64, 34, 25, 12, 22, 11, 90 };
sorter.SetStrategy(new QuickSortStrategy());
sorter.Sort(array2);
Console.WriteLine(string.Join(", ", array2));

Compression Strategy Example

public interface ICompressionStrategy
{
    void Compress(string file);
}

public class ZipCompressionStrategy : ICompressionStrategy
{
    public void Compress(string file)
    {
        Console.WriteLine($"Compressing {file} using ZIP");
    }
}

public class RarCompressionStrategy : ICompressionStrategy
{
    public void Compress(string file)
    {
        Console.WriteLine($"Compressing {file} using RAR");
    }
}

public class SevenZipCompressionStrategy : ICompressionStrategy
{
    public void Compress(string file)
    {
        Console.WriteLine($"Compressing {file} using 7Z");
    }
}

public class FileCompressor
{
    private ICompressionStrategy _strategy;

    public FileCompressor(ICompressionStrategy strategy)
    {
        _strategy = strategy;
    }

    public void SetStrategy(ICompressionStrategy strategy)
    {
        _strategy = strategy;
    }

    public void CompressFile(string file)
    {
        _strategy.Compress(file);
    }
}

Applications

Enterprise Use Cases:

  1. Payment Processing: Different payment methods (credit card, PayPal, bank transfer)
  2. Sorting Algorithms: Choosing sorting algorithm based on data characteristics
  3. Compression: Different compression algorithms (ZIP, RAR, 7Z)
  4. Validation: Different validation strategies
  5. Caching: Different caching strategies (LRU, LFU, FIFO)
  6. Routing: Different routing algorithms
  7. Discount Calculation: Different discount strategies
  8. Data Export: Different export formats (CSV, JSON, XML)

Benefits:

  • Eliminates conditional statements for algorithm selection
  • Makes algorithms interchangeable
  • Follows Open/Closed Principle—easy to add new strategies
  • Follows Single Responsibility Principle—each strategy has one algorithm
  • Allows algorithms to vary independently from clients
  • Enables runtime algorithm selection

When to Use:

  • You have multiple ways to perform a task
  • You want to avoid conditional statements for algorithm selection
  • You want to make algorithms interchangeable
  • Algorithms should be selected at runtime
  • You want to isolate algorithm implementation from client code

Strategy vs State:

  • Strategy: Algorithm is chosen externally, behavior changes based on selected strategy
  • State: Behavior changes based on internal state, state transitions are part of the object

Strategy vs Template Method:

  • Strategy: Uses composition, entire algorithm varies
  • Template Method: Uses inheritance, only parts of algorithm vary

Key Insight: The Strategy pattern encapsulates algorithms in separate classes. The context uses a strategy object to perform operations, allowing strategies to be swapped at runtime. This eliminates conditional logic and makes algorithms interchangeable, following the Open/Closed Principle.

Leave a Comment