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:
- Payment Processing: Different payment methods (credit card, PayPal, bank transfer)
- Sorting Algorithms: Choosing sorting algorithm based on data characteristics
- Compression: Different compression algorithms (ZIP, RAR, 7Z)
- Validation: Different validation strategies
- Caching: Different caching strategies (LRU, LFU, FIFO)
- Routing: Different routing algorithms
- Discount Calculation: Different discount strategies
- 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.
