<aside> 📚 📁 Category: Behavioral Pattern

</aside>

Overview

Strategy is a behavioral design pattern that lets you define a family of algorithms, put each of them into a separate class, and make their objects interchangeable. It enables selecting an algorithm's behavior at runtime instead of implementing a single algorithm directly.

<aside> â„šī¸ When to use: When you want to use different variants of an algorithm within an object and be able to switch from one algorithm to another during runtime. When you have a lot of similar classes that only differ in the way they execute some behavior.

</aside>

Problem

You're building a navigation app. Initially, it only builds routes for cars. Later, you add walking, cycling, and public transport options. Using if-else statements for each type creates messy code that's hard to maintain and test.

Solution

Extract all different algorithms into separate classes called strategies. The original class (context) stores a reference to one of the strategies and delegates work to the strategy object instead of executing it directly.

Implementation in C#

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

// Concrete Strategies
public class CreditCardPayment : IPaymentStrategy
{
    private string _cardNumber;
    private string _cvv;
    
    public CreditCardPayment(string cardNumber, string cvv)
    {
        _cardNumber = cardNumber;
        _cvv = cvv;
    }
    
    public void Pay(decimal amount)
    {
        Console.WriteLine($"Paid ${amount} using Credit Card ending in {_cardNumber.Substring(_cardNumber.Length - 4)}");
    }
}

public class PayPalPayment : IPaymentStrategy
{
    private string _email;
    
    public PayPalPayment(string email)
    {
        _email = email;
    }
    
    public void Pay(decimal amount)
    {
        Console.WriteLine($"Paid ${amount} using PayPal account {_email}");
    }
}

public class BitcoinPayment : IPaymentStrategy
{
    private string _walletAddress;
    
    public BitcoinPayment(string walletAddress)
    {
        _walletAddress = walletAddress;
    }
    
    public void Pay(decimal amount)
    {
        Console.WriteLine($"Paid ${amount} using Bitcoin to wallet {_walletAddress}");
    }
}
public class ShoppingCart
{
    private IPaymentStrategy _paymentStrategy;
    private decimal _total;
    
    public void SetPaymentStrategy(IPaymentStrategy strategy)
    {
        _paymentStrategy = strategy;
    }
    
    public void AddItem(decimal price)
    {
        _total += price;
    }
    
    public void Checkout()
    {
        if (_paymentStrategy == null)
        {
            throw new InvalidOperationException("Payment strategy not set");
        }
        
        _paymentStrategy.Pay(_total);
        _total = 0;
    }
}

// Usage
var cart = new ShoppingCart();
cart.AddItem(29.99m);
cart.AddItem(49.99m);

// Pay with credit card
cart.SetPaymentStrategy(new CreditCardPayment("1234567812345678", "123"));
cart.Checkout();

// Or pay with PayPal
cart.AddItem(99.99m);
cart.SetPaymentStrategy(new PayPalPayment("user@example.com"));
cart.Checkout();

Implementation in Python

from abc import ABC, abstractmethod

# Strategy interface
class PaymentStrategy(ABC):
    @abstractmethod
    def pay(self, amount: float):
        pass

# Concrete Strategies
class CreditCardPayment(PaymentStrategy):
    def __init__(self, card_number: str, cvv: str):
        self.card_number = card_number
        self.cvv = cvv
    
    def pay(self, amount: float):
        print(f"Paid ${amount} using Credit Card ending in {self.card_number[-4:]}")

class PayPalPayment(PaymentStrategy):
    def __init__(self, email: str):
        self.email = email
    
    def pay(self, amount: float):
        print(f"Paid ${amount} using PayPal account {self.email}")

class BitcoinPayment(PaymentStrategy):
    def __init__(self, wallet_address: str):
        self.wallet_address = wallet_address
    
    def pay(self, amount: float):
        print(f"Paid ${amount} using Bitcoin to wallet {self.wallet_address}")

# Context
class ShoppingCart:
    def __init__(self):
        self._payment_strategy = None
        self._total = 0.0
    
    def set_payment_strategy(self, strategy: PaymentStrategy):
        self._payment_strategy = strategy
    
    def add_item(self, price: float):
        self._total += price
    
    def checkout(self):
        if not self._payment_strategy:
            raise ValueError("Payment strategy not set")
        
        self._payment_strategy.pay(self._total)
        self._total = 0.0

# Usage
cart = ShoppingCart()
cart.add_item(29.99)
cart.add_item(49.99)

cart.set_payment_strategy(CreditCardPayment("1234567812345678", "123"))
cart.checkout()

Real-World Example: Sorting Strategies

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

public class QuickSort : ISortStrategy
{
    public void Sort(List<int> list)
    {
        Console.WriteLine("Sorting using QuickSort");
        list.Sort(); // Simplified
    }
}

public class MergeSort : ISortStrategy
{
    public void Sort(List<int> list)
    {
        Console.WriteLine("Sorting using MergeSort");
        // Merge sort implementation
    }
}

public class BubbleSort : ISortStrategy
{
    public void Sort(List<int> list)
    {
        Console.WriteLine("Sorting using BubbleSort");
        // Bubble sort implementation
    }
}

public class Sorter
{
    private ISortStrategy _strategy;
    
    public void SetStrategy(ISortStrategy strategy)
    {
        _strategy = strategy;
    }
    
    public void Sort(List<int> list)
    {
        _strategy.Sort(list);
    }
}

// Usage - choose strategy based on data size
var sorter = new Sorter();
var data = new List<int> { 5, 2, 8, 1, 9 };

if (data.Count < 10)
    sorter.SetStrategy(new BubbleSort());
else if (data.Count < 1000)
    sorter.SetStrategy(new QuickSort());
else
    sorter.SetStrategy(new MergeSort());

sorter.Sort(data);

Pros and Cons