The Strategy Pattern
Shelfwise's price_order has grown an if/elif chain: no discount, member 10% off, buy three get the cheapest free. The festival sale starts next week with a fourth rule, and nobody wants to edit a function that every order goes through. Strategy is the pattern built for exactly this.
The rule
Put each interchangeable algorithm in its own object, and hand in the one you need.
Strategy has three roles:
- Strategy interface: the one method every variant offers.
- Concrete strategies: one class per variant.
- Context: the code that uses a strategy without knowing which one it has.
class DiscountStrategy(ABC):
@abstractmethod
def discount(self, order): ...
class NoDiscount(DiscountStrategy):
def discount(self, order):
return 0
class MemberDiscount(DiscountStrategy):
def discount(self, order):
return order.subtotal() * 0.10
class ThreeForTwo(DiscountStrategy):
def discount(self, order):
prices = sorted(line.price for line in order.lines)
return prices[0] if len(prices) >= 3 else 0
def price_order(order, strategy: DiscountStrategy):
return order.subtotal() - strategy.discount(order)price_order is the context. It has no conditions at all. For the festival sale you write FestivalDiscount and pass it in; nothing else changes. Each rule is also small enough to unit-test alone.
(To keep the example short, ThreeForTwo treats each order line as one book. A real version would account for quantities.)
Strategy is the design principles you already know, packaged: encapsulate what varies (the discount rule), program against an abstraction (DiscountStrategy), composition (the context holds a strategy) and Open/Closed (new rules are new classes).
In Python, a strategy can even be a plain function: price_order(order, member_discount). Functions are values, so a one-method interface often collapses into a callable. The pattern is the same idea either way. Choose a class when a strategy needs its own settings, such as PercentOff(15), and a function when it does not.
The shortcut
Look for an if/elif chain that selects how to do one job, where the branches are alternatives to each other. Each branch becomes a strategy; the chain becomes one call.
Spot the chain
estimate(order)branches on "standard", "express" and "same-day" to compute a date.
The trap is using Strategy for a choice that never changes. If there is only ever one algorithm, a strategy interface is ceremony without benefit.
Drills
Answer fast — trust the rule.
In the Strategy pattern, what does the context know about the strategy it uses?
The festival sale needs a new discount rule. With Strategy in place, what do you change?
Which situation is the best fit for Strategy?
In Python, how else can a one-method strategy be passed in?
Remember
- Put each interchangeable algorithm in its own object, and hand in the one you need.
- A chain choosing how to do one job becomes one strategy per branch and one call.
- Trap: a strategy interface for a choice that never varies.
