Skip to main content

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
tip

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​

StrategyState
IntentSwap algorithmsChange behavior based on internal state
Who changes itClient (external)Object itself (internal)
KnowledgeClient knows which strategy to pickClient doesn't know about state transitions
AnalogyChoosing a payment methodA vending machine switching modes

.NET Real-World Usage​

  • IComparer<T> β€” swap sorting strategies without changing the sort algorithm
  • ILogger providers β€” different logging strategies (console, file, cloud)
  • IConfiguration providers β€” 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.

  • 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