Strategy
The problem
The shop runs different pricing rules at different times: loyalty discounts, bulk-order discounts,
seasonal sales, sometimes none. Hard-coding them into the price calculator produces a growing
if/else ladder that must be re-tested in full every time marketing invents a new promotion.
The pattern
Each rule is its own object behind one DiscountStrategy interface; the PriceCalculator context
is configured with a rule rather than containing the rules:
PriceCalculator januarySale = new PriceCalculator(Discounts.seasonalSale(20));
PriceCalculator everyday = new PriceCalculator(Discounts.loyalty());
DiscountStrategy is a @FunctionalInterface, so a one-off promotion is just a lambda – which is
how the pattern usually appears in modern Java (Comparator being the canonical example).
Benefits
- Swap algorithms at runtime – the active promotion is data/configuration, not code structure.
- Each rule tested in isolation – a strategy is a tiny pure function; no combinatorial calculator tests.
- Open/closed – a new promotion is a new strategy; the calculator and existing strategies are untouched.
- No conditional ladders – the dispatch is polymorphism, not
if (promoType == ...).
Seen in the wild
Comparator (sorting strategy), ThreadFactory, Spring Security’s PasswordEncoder, Jackson’s
PropertyNamingStrategy.
Implementation
DiscountStrategy– the strategy interface, a@FunctionalInterface.Discounts– the ready-made pricing rules.PriceCalculator– the context configured with a rule.