10.7. Strategy pattern

Fly strategy

Fly strategy

We can think of flying as a strategy different birds employ to move around. Birds don't inherit a fly behavior, they have it. The centerpiece of the solution is to isolate many different types of flying behavior behind a single class that stores a pointer to a function implementing the behavior. Each implementation defines a different strategy for the interface.

Any callable entity (function, function object, or lambda) is a potential strategy that derived classes of a bird can now use.

Fly strategy support code
#include <concepts>
#include <functional>
#include <iostream>
#include <utility>

// an alias to avoid copying std::function<void()> everywhere
using fly_strategy = std::function<void()>;

template <typename S>
concept fly_strategy_source =
  std::constructible_from<fly_strategy, S>;

class fly_behavior {
public:
  template <fly_strategy_source S>
  explicit fly_behavior(S&& new_strategy)
    : strategy_{std::forward<S>(new_strategy)}
  {
  }

  void fly() const {
    strategy_();
  }

  template <fly_strategy_source S>
  void strategy(S&& new_strategy) {
    strategy_ = std::forward<S>(new_strategy);
  }

private:
  fly_strategy strategy_;
};

// a function object that implements a strategy
struct soar
{
  void operator()() const {
    std::cout << "fly by soaring.\n";
  }
};

// a free function can also implement a strategy
void no_flying_allowed() {
  std::cout << "I don't fly.\n";
}

The concept fly_strategy_source is a real template requirement: any strategy must be a callable object that can be stored in a std::function<void()>. The fly_behavior class owns that callable and provides one place to execute or replace the current strategy.

The base class now delegates the fly behavior to the strategy instead of either defining a single fixed behavior or forcing every derived class to create one. In languages without lambda expressions, each implemented strategy is usually implemented as a separate class, each inheriting from the base strategy class. In C++, an inheritance based solution is possible, but not required. There is no 'best' solution - your needs must drive the final design decision. In general, if the strategy also needs to store state information, then implement as a class or function object. If the strategy is stateless, then implement a functional solution.

Bird base class using the fly strategy
class bird {
public:
  bird()
    : flight_{soar{}}
  {
  }

  template <fly_strategy_source S>
  explicit bird(S&& new_strategy)
    : flight_{std::forward<S>(new_strategy)}
  {
  }

  virtual ~bird() = default;

  template <fly_strategy_source S>
  void set_fly_behavior(S&& new_strategy) {
    flight_.strategy(std::forward<S>(new_strategy));
  }

  void fly() const {
    flight_.fly();
  }

private:
  fly_behavior flight_;
};

In this example, a bird may

  • default construct the default soaring strategy, or

  • set a strategy when constructed, or

  • change the strategy at some point after construction.

An example of birds using the strategy:

Birds with different fly strategies
// a hawk can use the default soar behavior
class hawk : public bird {
public:
  hawk() = default;
};

// this penguin defines its fly behavior using a free function
class penguin : public bird {
public:
  penguin()
    : bird{no_flying_allowed}
  {
  }
};

And now the reusable pieces can be combined into a complete program.

Hawk and penguin fly strategies
 1int main() {
 2  hawk h;
 3  h.fly();
 4
 5  penguin p;
 6  p.fly();
 7
 8  // change the behavior for just this penguin
 9  p.set_fly_behavior(
10    []() {
11      std::cout << "With a rocket pack, now I can fly!\n";
12    }
13  );
14  p.fly();
15}

Notice that we fixed our inheritance problem by using composition. Not only did composition allow us to encapsulate a family of behaviors, it also allowed a simple hook to enable changing the behavior at runtime.


More to Explore