Type Behavioral
Purpose Define a family of algorithms, encapsulate each one, and make them interchangeable. Strategy lets the algorithm vary independently from clients that use it.

Description

The Strategy pattern defines a family of algorithms, encapsulates each one, and makes them interchangeable. Rather than embedding multiple alternative algorithms inside a single host class, the pattern places each algorithm in its own class and allows the appropriate one to be selected as needed.

This is especially useful when several different approaches may be applied to the same problem. If all of those alternatives are kept inside one class, the result often becomes cluttered with conditional logic and is harder to extend or maintain. Strategy avoids this by separating the varying algorithmic behaviour from the rest of the system.

A strategy is selected from the available family and supplied to, or selected by, the context, which uses that strategy through a common interface. In some cases, the client and the context may effectively be the same object if little or no surrounding context is required. More commonly, however, the context performs the parts of the task that are common to all strategies and delegates only the variable portion to the selected strategy.

Strategy is primarily concerned with encapsulating interchangeable algorithms.


Overview

Strategy
Strategy Defines an interface common to all the strategies.
Context Uses a Strategy through its common interface and provides any context required for the algorithm to perform its work.
ConcreteStrategy Implementations of the Strategy interface.
Client Selects or configures the strategy to be used and interacts with the context.

Implementation

The following example uses an ITaxCalculator strategy which three concrete strategy classes implement. The context determines which strategy to use based on the customer's country.

The tax rates and calculations in this example are deliberately simplified for demonstrating the Strategy pattern and should not be interpreted as actual tax calculations.

// Domain

public class TaxCustomer(string name, string country, decimal gross)
{
    public string Name { get; set; } = name;    
    public string Country { get; set; } = country;    
    public decimal Gross { get; set; } = gross;    
    public decimal Tax { get; set; } = 0;    
    public decimal Net => Gross - Tax;
}

// Strategy

public interface ITaxCalculator
{
    decimal Execute(TaxCustomer customer);
}

// Concrete Strategies

public class AustralianTaxCalculator : ITaxCalculator
{
    public decimal Execute(TaxCustomer customer) => customer.Gross switch
    {
        < 18200 => 0,
        < 45000 => customer.Gross * 0.19m,
        < 120000 => customer.Gross * 0.325m,
        _ => customer.Gross * 0.37m
    };
}

public class UkTaxCalculator : ITaxCalculator
{
    public decimal Execute(TaxCustomer customer) => customer.Gross switch
    {
        < 12500 => 0,
        < 50000 => customer.Gross * 0.20m,
        < 150000 => customer.Gross * 0.40m,
        _ => customer.Gross * 0.45m
    };
}

public class SgTaxCalculator : ITaxCalculator
{
    public decimal Execute(TaxCustomer customer) => customer.Gross switch
    {
        < 20000 => 0,
        < 40000 => customer.Gross * 0.10m,
        < 80000 => customer.Gross * 0.20m,
        _ => customer.Gross * 0.30m
    };
}

// Context

public class TaxContext
{
    public void AssignTax(TaxCustomer customer)
    {
        ITaxCalculator strategy = customer.Country switch
        {
            "Australia" => new AustralianTaxCalculator(),
            "Singapore" => new SgTaxCalculator(), 
            _ => new UkTaxCalculator(),
        };
        
        customer.Tax = strategy.Execute(customer);
    }
}

// Client 

public class TaxAccountant
{
    private readonly TaxContext _context = new TaxContext();
    
    public void CalculateTax(TaxCustomer customer)
        => _context.AssignTax(customer);
}

// Demonstration

public static class StrategyDemo
{
    public static void Execute()
    {
        var accountant = new TaxAccountant();
        
        var john = new TaxCustomer("John", "Australia", 60000);
        accountant.CalculateTax(john);
        DisplaySalaryInfo(john);
        
        var jane = new TaxCustomer("Jane", "UK", 60000);
        accountant.CalculateTax(jane);
        DisplaySalaryInfo(jane);
        
        var jill = new TaxCustomer("Jill", "Singapore", 60000);
        accountant.CalculateTax(jill);
        DisplaySalaryInfo(jill);
    }

    private static void DisplaySalaryInfo(TaxCustomer customer)
    {
        var culture = customer.Country switch
        {
            "Australia" => "en-AU",
            "Singapore" => "en-SG",
            _ => "en-GB",
        };
        
        var info = new CultureInfo(culture, false);
        var gross = string.Format(info, "{0:c0}", customer.Gross);
        var tax = string.Format(info, "{0:c0}", customer.Tax);
        var net = string.Format(info, "{0:c0}", customer.Net);
        var details = $"Gross: {gross}, Tax: {tax}, Net: {net}";

        Console.WriteLine($"{customer.Name} ({customer.Country}) {details}");
    }
}

// outputs:

// John (Australia) Gross: $60,000, Tax: $19,500, Net: $40,500
// Jane (UK) Gross: £60,000, Tax: £24,000, Net: £36,000
// Jill (Singapore) Gross: $60,000, Tax: $12,000, Net: $48,000


Summary

For more information, please see here.