Daniel Carvalho
Daniel Carvalho

Reputation: 483

How to instantiate input based strategy pattern

The instantiation of strategy pattern is usually completely overlooked by the examples. Let's assume there is an input that defines which class to use. We'd have something along the lines of:

class Strategy {
    Strategy(){}
    virtual void runAlgorithm() = 0;
};

class A : public Strategy {
    A () {}
    static bool isA(char input){ return input == 'A'; }
    void runAlgorithm { /* Do A algorithm */ }
};

class B : public Strategy {
    B () {}
    static bool isB(char input){ return input == 'B'; }
    void runAlgorithm { /* Do B algorithm */ }
};

// Other algorithms

Strategy* createStrat(char input){
    Strategy* instance;

    // Define which algorithm to use
    if (A::isA(input)) {
        instance = A();
    } else if (B::isB(input)) {
        instance = B();
    } ...

    // Run algorithm
    instance.runAlgorithm();

    return instance;
}

As you can see, if we have multiple different algorithms this if/switch can get pretty big. Is there a pattern to make this code easier to humanly parse (i.e., a for loop and a call) without adding an array of pairs? The question could also be expanded to "How to instantiate an input based strategy pattern?"

Do not limit to this code, as it is just an example.

Upvotes: 2

Views: 248

Answers (2)

linuxfever
linuxfever

Reputation: 3823

Ok, if you know all your strategies in advance you can use a very simple meta-programming recursion to unroll your if-else chains automatically. Here we go:

#include <string_view>
#include <iostream>
#include <exception>
#include <memory>

struct Strategy {
    Strategy(){}
    virtual void runAlgorithm() = 0;
};

struct A : public Strategy {

    A () {std::cout << "Creating A" << std::endl;}

    static constexpr std::string_view id(){
        return std::string_view("A");
    }

    void runAlgorithm() { /* Do A algorithm */ }
};

struct B : public Strategy {

    B () {std::cout << "Creating B" << std::endl;}

    static constexpr std::string_view id(){
        return std::string_view("B");
    }

    void runAlgorithm() { /* Do B algorithm */ }
};

struct C : public Strategy {

    C () {std::cout << "Creating C" << std::endl;}

    static constexpr std::string_view id(){
        return std::string_view("C");
    }

    void runAlgorithm() { /* Do C algorithm */ }
};

// the if else chains are constructed by recursion
template <class Head, class... Tail>
struct factory {

  static std::unique_ptr<Strategy> call(std::string id) {
      if(Head::id() == id) return std::make_unique<Head>();
      else return factory<Tail...>::call(id);
  }
};

// specialization to end the recursion
// this throws an exception, but you can adapt it to your needs
template <class Head>
struct factory<Head> {

  static std::unique_ptr<Strategy> call(std::string id) {
      if(Head::id() == id) return std::make_unique<Head>();
      else throw std::invalid_argument("The strategy id you selected was not found.");
  }
};

// here is your factory which can create instances of A,B,C based only on the runtime id
using my_factory = factory<A,B,C>;

int main() {

    auto Astrategy = my_factory::call("A");
    auto Bstrategy = my_factory::call("B");
    auto Cstrategy = my_factory::call("C");
    my_factory::call("D"); // exception thrown

}

Live code here

Edit

Edited to account for smart pointers and error checking as Jarod42 suggested.

Upvotes: 2

Marek R
Marek R

Reputation: 37697

Yes there is a solution for that. There are couple ways to do it. Template as in other class or this way:

class StrategyMaker {
public:
     void register(std::funcion<bool(char)> predicate,
                   std::function<std::unique_ptr<Strategy>(char)> factory) {
           mFactories.push_back(std::make_pair(std::move(predicate), std::move(factory)));
     }

     std::unique_ptr<Strategy> create(char ch) {
         for (auto pair & : mFactories) {
             if (pair.first(ch)) {
                 return pair.second(ch);
             }
         }
         return {};
     }

private:
     std::vector<std::pair<std::funcion<bool(char)>,
                           std::function<std::unique_ptr<Strategy>(char)>>> mFactories;
};

StrategyMaker maker;
maker.register([](char ch){ return input == 'A'; },
               [](char ch){ return std::unique_ptr(new A); });

Once I've seen nice article showing how to make it fully automatic. I've found something like this (I'm not 100% sure this is what I read some time ago).

Upvotes: 1

Related Questions