Strategy in C++

Behavioral Easy

In one sentence: Strategy defines a family of algorithms, wraps each one in its own class behind a shared interface, and makes them interchangeable — so the algorithm can vary independently of the clients that use it. (GoF also calls it Policy.)

Real-world analogy

You have a flight at six, so you need to get to the airport. A taxi will do it. So will the airport bus. So will the bicycle, if you travel light. Same starting point, same destination, same single instruction — arrive — and three completely different ways of carrying it out.

Which one you take is your decision, made this morning, on this morning's grounds: what it costs, how long it takes, how much luggage you're dragging. The trip itself has no opinion. Your calendar entry says "leave for the airport at 4:30" and stops there; it doesn't know about fares, timetables, or bike locks, and it wouldn't read differently if you changed your mind at the door.

Each method is self-contained — everything the taxi option involves lives inside "take a taxi." So next month you can pick a different one without relearning how to travel, and when a new express train line opens it becomes one more thing you can choose, not a revision of the plan.

Mapping the analogy to the pattern:

  • "Get to the airport" → the one operation the Strategy interface declares; every option promises exactly this and nothing more.
  • Taxi, bus, bicycle → the ConcreteStrategies, each holding its own complete method, none knowing the others exist.
  • You picking this morning's mode → the Client configuring the Context: the choice belongs to the customer, and the customer alone.
  • The calendar entry that just says "leave at 4:30" → the Context, forwarding the request without ever asking which method it's holding.
  • The new express line → a new ConcreteStrategy, added without touching the Context or any existing option.

Another angle: paying at the till. Cash, card, or phone — the shop needs the amount settled and doesn't care how; you pick per purchase, and a new payment method is a new option at the counter, not a new shop.

The problem

GoF's example is breaking a stream of text into lines: there are many algorithms for it, and hard-wiring them into the class that needs them makes that class bigger, drags in algorithms nobody uses, and makes adding a new one awkward. Ours is a checkout page quoting a shipping price. Standard post, express courier, and pickup-point collection all price differently.

  1. One method swells. A single cost() with a branch per carrier grows every time sales adds an option.
  2. Unrelated changes collide. Tweaking the express formula means editing the same function that handles the others — and re-testing all of them.
  3. Runtime choice is awkward. The carrier isn't known until the customer picks one, so the branch has to be re-evaluated everywhere the price is needed.

The solution

Give each algorithm its own class:

  1. Define a strategy interface with the one operation that varies — here, cost(parcel).
  2. Implement one class per algorithm, each holding only its own formula.
  3. The context (the checkout) stores a pointer to a strategy and calls it, never asking which one it has.

Adding a carrier is adding a class. The checkout is never touched.

There is one design decision worth pausing on: how does the strategy get the data it needs? GoF calls the two options "taking the data to the strategy" — the context passes everything as parameters, keeping the two decoupled but sometimes passing data a given strategy ignores — and having the context pass itself, so the strategy asks for exactly what it wants. The second requires the context to expose a richer interface, which couples the two more tightly. The example below takes the data to the strategy: a Parcel goes in, a price comes out.

Note: GoF singles out C++ templates as a second way to do this. Make the context a template parameterized on its strategy (template <class AStrategy> class Context), and you bind the strategy statically — no abstract base class, no virtual call, more efficiency. The catch is in the trade: this only works when the strategy is known at compile time and never changes at runtime. In modern C++ the lighter runtime option is a std::function<double(const Parcel&)> member assigned a lambda — the same pattern with far less ceremony. Reach for the interface version when strategies carry state, need names, or come from a plugin registry.

Structure

Structure of the Strategy pattern Structure of the Strategy pattern
Structure of the Strategy pattern

Three participants, and the interesting relationship is composition rather than inheritance:

  • Strategy — declares the interface common to all supported algorithms. The context calls the algorithm only through this interface. ShippingStrategy here; GoF's is Compositor.
  • ConcreteStrategy — implements one algorithm behind that interface. Standard, Express, and PickupPoint.
  • Context — is configured with a ConcreteStrategy object, keeps a reference to it, and forwards client requests to it. It may also expose an interface letting the strategy pull data back out of it. Checkout here; GoF's is Composition.

Clients typically create a ConcreteStrategy, hand it to the context, and from then on talk only to the context.

C++ example

A checkout that quotes the same parcel three ways, changing only which strategy it holds.

#include <iomanip>
#include <iostream>
#include <memory>
#include <string>

struct Parcel {
    double weightKg;
    double distanceKm;
};

class ShippingStrategy {
public:
    virtual ~ShippingStrategy() = default;
    virtual double cost(const Parcel& p) const = 0;
    virtual std::string name() const = 0;
};

class Standard : public ShippingStrategy {
public:
    double cost(const Parcel& p) const override { return 3.0 + 0.5 * p.weightKg; }
    std::string name() const override { return "Standard"; }
};

class Express : public ShippingStrategy {
public:
    double cost(const Parcel& p) const override {
        return 8.0 + 1.2 * p.weightKg + 0.05 * p.distanceKm;
    }
    std::string name() const override { return "Express"; }
};

class PickupPoint : public ShippingStrategy {
public:
    double cost(const Parcel&) const override { return 1.5; }
    std::string name() const override { return "Pickup point"; }
};

class Checkout {
public:
    void setStrategy(std::unique_ptr<ShippingStrategy> s) { strategy_ = std::move(s); }
    void quote(const Parcel& p) const {
        std::cout << std::fixed << std::setprecision(2)
                  << strategy_->name() << ": $" << strategy_->cost(p) << "\n";
    }

private:
    std::unique_ptr<ShippingStrategy> strategy_;
};

int main() {
    Parcel parcel{2.0, 300.0};
    Checkout checkout;

    checkout.setStrategy(std::make_unique<Standard>());
    checkout.quote(parcel);

    checkout.setStrategy(std::make_unique<Express>());
    checkout.quote(parcel);

    checkout.setStrategy(std::make_unique<PickupPoint>());
    checkout.quote(parcel);
}
Output
Standard: $4.00
Express: $25.40
Pickup point: $1.50

quote() is called with the same parcel three times and contains no conditionals at all — the price changed because the strategy did.

When should you use it?

GoF's applicability list has four entries. Use Strategy when:

  • Many related classes differ only in their behavior. Instead of a subclass per behavior, configure one class with one of several strategy objects.
  • You need different variants of an algorithm — for instance implementations trading space against time. A class hierarchy of algorithms is the natural way to hold them.
  • An algorithm uses data that clients have no business seeing. Putting it behind a strategy keeps complex, algorithm-specific data structures out of the client's view.
  • A class defines many behaviors that show up as multiple conditional statements in its operations. Move each branch into its own strategy class.

Pros and cons

Pros (GoF's consequences)

  • Families of related algorithms. A Strategy hierarchy is a reusable family, and inheritance inside it can factor out what the algorithms share.
  • An alternative to subclassing the context. You could subclass the context itself to vary its behavior, but that hard-wires the algorithm into the context, mixes the two concerns, and leaves you unable to change the algorithm dynamically. Separate Strategy classes let the algorithm vary independently of its context.
  • Strategies eliminate conditional statements. When several behaviors live in one class, a conditional to select between them is nearly unavoidable. GoF puts it the other way round as a diagnostic: code full of conditionals is often code asking for Strategy.
  • A choice of implementations. The same behavior offered with different time and space trade-offs, chosen by the client.

Cons (also GoF's)

  • Clients must be aware of the different strategies to pick the right one, which can expose them to implementation concerns. GoF's rule of thumb: only use Strategy when the variation in behavior is genuinely relevant to clients.
  • Communication overhead between Strategy and Context. All concrete strategies share one interface, so it has to be wide enough for the most demanding of them. Simple strategies will then ignore parameters the context carefully computed — PickupPoint below never looks at the parcel at all.
  • More objects. Strategy raises the object count. You can claw that back by making strategies stateless so contexts can share them (see Flyweight), keeping any residual state in the context and passing it in on each call.
  • A virtual call per invocation can matter in the hottest loops — measure before worrying.

How to remember it

Memory hook: "Same trip, different transport." The destination is fixed; you choose whether to drive, take the train, or cycle, and the journey doesn't care. The two confusable siblings both hinge on who decides: with State the object changes its own behavior as it moves between modes, while with Strategy the client picks the algorithm and the context never chooses for itself. And against Template Method — inheritance fills in the blanks of a fixed recipe, composition swaps the whole recipe.

  • Flyweight — GoF's one listed relation: strategy objects often make good flyweights, since a stateless strategy can be shared by every context that needs it.
  • Template Method — GoF draws the contrast directly: template methods use inheritance to vary part of an algorithm; strategies use delegation to vary the whole of it.
  • State — the same class diagram with a different intent, and the pattern most often mistaken for this one.