Strategy Pattern
Definitionβ
The Strategy defines a family of algorithms, encapsulates each one, and makes them interchangeable. Strategy lets the algorithm vary independently from the clients that use it.
Coffee Shop Exampleβ
Our Coffee Shop supports multiple pricing strategies β regular pricing, happy hour discounts, loyalty member pricing, and seasonal promotions. Instead of a giant if/else block, each strategy is its own class. The Cashier delegates pricing to whichever strategy is active.
Structureβ
Strategy Interfaceβ
public interface IPricingStrategy
{
decimal CalculatePrice(decimal basePrice, CupSize size);
string GetName();
}
Concrete Strategiesβ
public class RegularPricing : IPricingStrategy
{
private static readonly Dictionary<CupSize, decimal> _markup = new()
{
[CupSize.Small] = 0.0m,
[CupSize.Medium] = 0.50m,
[CupSize.Large] = 1.00m,
};
public decimal CalculatePrice(decimal basePrice, CupSize size) =>
basePrice + _markup[size];
public string GetName() => "Regular Pricing";
}
public class HappyHourPricing : IPricingStrategy
{
private readonly decimal _discount;
public HappyHourPricing(decimal discount = 0.30m)
{
_discount = discount;
}
public decimal CalculatePrice(decimal basePrice, CupSize size) =>
basePrice * (1 - _discount); // Flat discount, size doesn't matter
public string GetName() => $"Happy Hour ({_discount:P0} off)";
}
public class LoyaltyPricing : IPricingStrategy
{
private readonly int _pointsPerDollar = 10;
private static readonly Dictionary<CupSize, decimal> _markup = new()
{
[CupSize.Small] = 0.0m,
[CupSize.Medium] = 0.50m,
[CupSize.Large] = 1.00m,
};
public decimal CalculatePrice(decimal basePrice, CupSize size)
{
var price = basePrice + _markup[size];
return price * 0.9m; // 10% loyalty discount
}
public string GetName() => "Loyalty Member (10% off)";
}
public class SeasonalPricing : IPricingStrategy
{
private readonly string _season;
private readonly decimal _surcharge;
public SeasonalPricing(string season, decimal surcharge)
{
_season = season;
_surcharge = surcharge;
}
public decimal CalculatePrice(decimal basePrice, CupSize size) =>
basePrice + _surcharge;
public string GetName() => $"{_season} Season (+${_surcharge:F2})";
}
Context β The Cashierβ
public class Cashier
{
private IPricingStrategy _strategy;
public Cashier(IPricingStrategy strategy)
{
_strategy = strategy;
}
public void SetStrategy(IPricingStrategy strategy) => _strategy = strategy;
public void Charge(string coffee, decimal basePrice, CupSize size)
{
var finalPrice = _strategy.CalculatePrice(basePrice, size);
Console.WriteLine($"[{_strategy.GetName()}] {size} {coffee}: ${basePrice:F2} β ${finalPrice:F2}");
}
}
Client β Switching Strategies at Runtimeβ
var cashier = new Cashier(new RegularPricing());
// Morning β regular pricing
cashier.Charge("Latte", 4.00m, CupSize.Medium);
cashier.Charge("Espresso", 2.50m, CupSize.Small);
// 2-4 PM β happy hour!
cashier.SetStrategy(new HappyHourPricing(0.30m));
cashier.Charge("Latte", 4.00m, CupSize.Medium);
cashier.Charge("Espresso", 2.50m, CupSize.Small);
// Loyalty member walks in
cashier.SetStrategy(new LoyaltyPricing());
cashier.Charge("Latte", 4.00m, CupSize.Large);
// Winter seasonal pricing
cashier.SetStrategy(new SeasonalPricing("Winter", 0.50m));
cashier.Charge("Latte", 4.00m, CupSize.Medium);
// Output:
// [Regular Pricing] Medium Latte: $4.00 β $4.50
// [Regular Pricing] Small Espresso: $2.50 β $2.50
// [Happy Hour (30% off)] Medium Latte: $4.00 β $2.80
// [Happy Hour (30% off)] Small Espresso: $2.50 β $1.75
// [Loyalty Member (10% off)] Large Latte: $4.00 β $4.50
// [Winter Season (+$0.50)] Medium Latte: $4.00 β $4.50
The Cashier doesn't know how the price is calculated. It delegates to whatever strategy is injected. Adding a new pricing rule means creating a new class β zero changes to Cashier (OCP).
Strategy vs Stateβ
| Strategy | State | |
|---|---|---|
| Intent | Swap algorithms | Change behavior based on internal state |
| Who changes it | Client (external) | Object itself (internal) |
| Knowledge | Client knows which strategy to pick | Client doesn't know about state transitions |
| Analogy | Choosing a payment method | A vending machine switching modes |
.NET Real-World Usageβ
IComparer<T>β swap sorting strategies without changing the sort algorithmILoggerproviders β different logging strategies (console, file, cloud)IConfigurationproviders β different configuration sources (JSON, env, Azure Key Vault)- ASP.NET Core authentication schemes β different auth strategies per request
List<T>.Sort(IComparer<T>)β the classic Strategy in the BCL
When to Useβ
- You have multiple variants of an algorithm and need to switch at runtime
- You want to avoid complex conditional logic (
if/switch) for algorithm selection - Algorithms should be reusable across different contexts
When NOT to Useβ
- There's only one algorithm β no need for the abstraction
- The algorithm rarely changes β a simple method is enough
- Strategies differ only by a parameter β use a single class with configuration
Key Takeawaysβ
- Strategy encapsulates each algorithm behind a common interface
- Algorithms are swapped at runtime β no code changes needed
- Eliminates large conditional blocks β each branch becomes a strategy class
- The context delegates to the strategy β it doesn't know the implementation details
Interview Questionsβ
Q: How is Strategy different from Template Method? Strategy uses composition β you inject different algorithm objects. Template Method uses inheritance β subclasses override specific steps. Strategy is more flexible; Template Method is simpler when the algorithm structure is fixed.
Q: How does Strategy relate to Dependency Injection? In ASP.NET Core, you register the strategy in DI:
services.AddScoped<IPricingStrategy, HappyHourPricing>();
To switch strategies dynamically, inject IEnumerable<IPricingStrategy> and select at runtime, or use a factory.
Q: What's the downside of Strategy? Clients must be aware of the available strategies to choose the right one. If there are many strategies, the decision logic can become complex. A Factory or Strategy Registry can help.
Related Topicsβ
- Template Method β algorithm skeleton via inheritance
- State β similar structure, behavior driven by internal state
- Factory Method β often used to create strategy objects
- SOLID β OCP β new strategies don't break existing code