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: cityStep-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 <|.. CheapestCommon 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, cityInterview 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.