Strategy Desing Pattern and variadic class templates

The Strategy design pattern is a perfect example of the OOP tenet that says: “favor composition over inheritance”. Inheritance is indeed powerful when it comes to code reuse, or when we want to treat objects polymorphically, but it also creates a tight coupling between classes, making them difficult to mantain. Composition is more flexible, allowing us to choose and change behavior dynamically at runtime, and makes our classes easier to maintain and extend.

The pattern

Let’s recap the strategy pattern in its classical implementation: the context mantains a reference to a strategy and when clients request an operation on the context object, this delegates the operation to the strategy it references. This decouples the context from the different strategies and allows to change strategy at runtime. The context can pass data to the strategy, in order to perform the algorithm. Alternatively, it can pass iself, but in this case it should expose an interface for the strategy to access its state:

 

Strategy pattern and templates

If the strategy doesn’t need to change at runtime, we can use templates with the strategy pattern: the Context is a class template that is parameterized on the type of the strategy; in this case the concrete strategy classes don’t need to share a base abstract class that defines a common explicit inteface, but they need to expose the required operations:

#include <string>
#include <iostream>
#include <utility>

template <typename T>
class Context;

class Strategy1
{
public:
    Strategy1(int i, double d) : i(i), d(d) {}
    void DoAlgorithm(Context<Strategy1> &context) { std::cout << "strategy 1 algorithm: " << i << " " << d << std::endl; }
private:
    int i;
    double d;
};

class Strategy2
{
public:
    Strategy2(const std::string &s) : s(s) {}
    void DoAlgorithm(Context<Strategy2> &context) { std::cout << "strategy 2 algorithm: " << s << std::endl; }
private:
    std::string s;
};

template <typename Strategy>
class Context
{
public:
    template <typename... Args>
    Context(Args&&...);
    void DoAlgorithm() { strategy.DoAlgorithm(*this); }
private:
    Strategy strategy;
};

template <typename S>
template <typename... Args>
Context<S>::Context(Args... args) : strategy(std::forward<Args>(args)...)
{
}

In this case the common (implicit) interface of the strategy classes consists of the DoAlgorithm() memeber function.The type of the strategy is set at compile time, when the Context class template is intantiated with the strategy type:

template <typename Strategy>
class Context
{
public:
    template <typename... Args>
    Context(Args&&...);
    void DoAlgorithm() { strategy.DoAlgorithm(*this); }
private:
    Strategy strategy;
};

The Context can be instantiated with any type, as long as the template argument is a class that provides a DoAlgorithm() operation, which takes a reference to the context as argument. Remember that the context can pass itself, or the relevant data to the strategy, which is used by the strategy to perform the computation, or sometimes it just doesn’t pass anything; in this example case the strategy interface takes a reference to an instantiation of the Context class template: note that the Context is obviuosly instantiated with the type of the strategy itself:

class Strategy1
{
// ...
    void DoAlgorithm(Context<Strategy1> &context);
// ...
};

Unlike base abstract classes that cannot be instantiated,  objects of concrete strategy classes can be created, so the Context class doesn’t necessarily need to hold a pointer or reference to a strategy, but it can contain an instance of a strategy class. Creating an instance of the Context class automatically calls the contructor for the strategy object it contains, but we don’t know in advance which type of strategy the object belongs to (we know it at compile time, when we instantiate the Context class template with a specific type), so we need to define a generic version of the Context constructor:

template <typename Strategy>
class Context
{
// ...
    template <typename... Args>
    Context(Args&&...args) : strategy(std::forward<Args>(args)...) {}
// ...
};

Using a variadic member template as constructor we can forward the arguments to the Context constructor (indeed the arguments are perfectly forwarded using std::forward), so the strategy object can be constructed when an instance of Context is created. In this example Strategy1 needs an int and a double, while Strategy2 uses a std::string object:

class Strategy1
{
public:
    Strategy1(int i, double d) : i(i), d(d) {}
    void DoAlgorithm(Context<Strategy1> &context) { std::cout << "strategy 1 algorithm: " << i << " " << d << std::endl; }
private:
    int i;
    double d;
};

class Strategy2
{
public:
    Strategy2(const std::string &s) : s(s) {}
    void DoAlgorithm(Context<Strategy2> &context) { std::cout << "strategy 2 algorithm: " << s << std::endl; }
private:
    std::string s;
};

This is a contrived example, but it shows that different strategies could possibly need different data to perform their algorithm, so they need different constructors (not necessarily default ones).

Leave a Reply

Your email address will not be published. Required fields are marked *