Strategy

Learning Objectives

Encapsulate interchangeable algorithms behind a contract.

Implement Strategy with interfaces, function objects and templates.

Choose between Strategy, conditionals and compile-time policies.

Prerequisites: Modules 8, 19.

Intuition and Definition

Intent: define a family of algorithms, encapsulate each one, and make them interchangeable; Strategy lets the algorithm vary independently from clients that use it.

Participants: Strategy interface, ConcreteStrategy implementations, Context (holds a strategy and delegates the varying step to it).

Why It Matters

Pricing rules, routing algorithms, compression codecs and validation policies change independently of the code that uses them. Strategy isolates each rule and lets you choose at run time (per customer, per configuration).

Syntax and Language Notes

# Python: a strategy is usually just a callable
def route(points, metric=lambda a, b: abs(a - b)): ...
type PricingRule = (subtotal: number) => number;
const happyHour: PricingRule = (s) => s * 0.8;
public interface IShippingCost { decimal For(Order o); }   // or Func<Order, decimal>

Complete Example

#include <iostream>
#include <memory>
#include <string>
#include <vector>

struct Leg { std::string name; double km; double minutes; double tollEuro; };

class RouteCost {                                        // Strategy
public:
    virtual ~RouteCost() = default;
    virtual double cost(const Leg& l) const = 0;
    virtual std::string name() const = 0;
};
class Fastest : public RouteCost {
public:
    double cost(const Leg& l) const override { return l.minutes; }
    std::string name() const override { return "fastest"; }
};
class Shortest : public RouteCost {
public:
    double cost(const Leg& l) const override { return l.km; }
    std::string name() const override { return "shortest"; }
};
class Cheapest : public RouteCost {                      // a strategy with its own parameter
    double euroPerKm_;
public:
    explicit Cheapest(double e) : euroPerKm_(e) {}
    double cost(const Leg& l) const override { return l.km * euroPerKm_ + l.tollEuro; }
    std::string name() const override { return "cheapest"; }
};

class Navigator {                                        // Context
    const RouteCost* strategy_;
public:
    explicit Navigator(const RouteCost& s) : strategy_(&s) 
…
fastest: motorway
shortest: city
cheapest: city

Step-by-Step Walkthrough

Navigator::choose is written once; it asks the strategy for a cost per leg.

Fastest minimises minutes (motorway, 28), shortest minimises km (city, 25), cheapest computes fuel + toll: motorway 42 × 0.18 + 6.50 = 14.06 €, country road 5.58 €, city 4.50 € → city.

Strategies can carry configuration (euroPerKm), which a simple switch would have to pass around.

classDiagram
  class Navigator {
    +choose(options) Leg
    +setStrategy(s)
  }
  class RouteCost {
    <<interface>>
    +cost(leg) double
  }
  Navigator o-- RouteCost
  RouteCost <|.. Fastest
  RouteCost <|.. Shortest
  RouteCost <|.. Cheapest

Common Mistakes

Strategy for two fixed branches that will never change — an if is simpler.

Strategies that need half the context's internals — the interface is wrong; pass the data they need explicitly.

Clients still switching on strategy type — then nothing was gained.

Trade-offs and Alternatives

Few, stable cases Strategy objects / std::function Run-time choice, many or growing cases, configurable rules Template policy (Navigator<Fastest>)

Choice fixed at compile time; zero overhead State (Module 43)

The behaviour changes because of the object's own state transitions

Testing: each strategy is tested alone with plain inputs; the context is tested with a stub strategy.

Real-World Use

std::sort with a comparator, payment-fee calculators, compression algorithms in archivers, retry/back-off policies, validation rules, AUTOSAR-style configurable filters.

Practice

Q1. (Easy) Which participant holds the strategy?

Answer: The context.

Q2. (Medium) Strategy vs State?

Answer: Strategy is chosen by the client to vary an algorithm; State is changed by the object itself as its internal state changes, and the states usually know their successors.

Q3. (Advanced) Express the same three strategies as lambdas and pass them to a function template.

#include <iostream>
#include <string>
#include <vector>
struct Leg { std::string name; double km, minutes, toll; };
template <class CostFn>
const Leg& choose(const std::vector<Leg>& opts, CostFn cost) {
    const Leg* best = &opts.front();
    for (const Leg& l : opts) if (cost(l) < cost(*best)) best = &l;
    return *best;
}
int main() {
    std::vector<Leg> o{{"motorway", 42, 28, 6.5}, {"country road", 31, 39, 0}, {"city", 25, 51, 0}};
    std::cout << choose(o, [](const Leg& l) { return l.minutes; }).name << ", "
              << choose(o, [](const Leg& l) { return l.km; }).name << ", "
              << choose(o, [](const Leg& l) { return l.km * 0.18 + l.toll; }).name << "\n";
}
motorway, city, city

Interview Question

Q1. "How does Strategy support the Open/Closed principle?"

Answer: The context depends on the strategy *interface*; new algorithms are added as new implementations without modifying the context or existing strategies, which stay closed to change while the system stays open to extension. The context and each strategy can be tested independently.

Quick Quiz

Q1. What varies in Strategy?

Answer: An algorithm (one step or the whole computation).

Q2. Name a standard-library function that takes a strategy.

Answer: std::sort with a comparator (or std::unordered_map with a hasher).

Q3. Compile-time variant of Strategy in C++?

Answer: Policy-based design with templates.

Summary

Strategy encapsulates interchangeable algorithms behind one contract.

Use objects, functions or template policies depending on when the choice is made.

Related: Modules 26, 43, 44, 58.