Skip to content

// Design Patterns · 21

Strategy

Many ways to do one jobleads toSwap them at runtime

Pick an algorithm at runtime behind one interface.

How it fits together

Strategy structure«client»ShippingCalculatorCalculate(order)StandardShipping«interface»ShippingCostStrategyCost(order)ExpressShippingStorePickuppicks by key
uses / callsimplements
  • ShippingCalculator uses / calls ShippingCostStrategy (picks by key)
  • StandardShipping implements ShippingCostStrategy
  • ExpressShipping implements ShippingCostStrategy
  • StorePickup implements ShippingCostStrategy

Spot it

  • A switch on type that grows every sprint
  • The same if/else chain copied in several places
  • Adding a variant means editing old code

It costs

More types and indirection; overkill for two branches that never change.

In code

public enum ShippingMethod { Standard, Express, StorePickup }​public sealed record Order(ShippingMethod Method, decimal TotalWeightKg);​public interface IShippingCostStrategy{    ShippingMethod Method { get; }    decimal Cost(Order order);}​public sealed class StandardShipping : IShippingCostStrategy{    public ShippingMethod Method => ShippingMethod.Standard;​    public decimal Cost(Order order) => order.TotalWeightKg < 2m ? 4.95m : 7.95m;}​// ExpressShipping and StorePickup follow the same shape.​public sealed class ShippingCalculator(IEnumerable<IShippingCostStrategy> strategies){    private readonly IReadOnlyDictionary<ShippingMethod, IShippingCostStrategy> _byMethod =        strategies.ToDictionary(s => s.Method);​    public decimal Calculate(Order order) => _byMethod[order.Method].Cost(order);}
Go deeperThe full explanation and what to ask for · 1 min readExpand

A strategy is one interchangeable algorithm behind a shared interface. The caller (the context) knows that it needs a price or a sort order, not how it is computed.

Watch for a strategy interface with a single implementation and no second one in sight, or strategies that need to reach into the context to work. Both mean the seam is in the wrong place.

In most languages a plain function value is the lightest strategy: a lambda or delegate passed in. When variants come from a DI container, resolve them by key instead of writing a factory by hand.

Ask for it

  • strategy interface
  • context
  • keyed lookup
  • open/closed
  • dependency injection

Replace the switch on ShippingMethod in ShippingCalculator with one ShippingCostStrategy per method, register each under its method key in the DI container, and resolve the right one by key. Add a unit test per strategy.