| 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 | 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.