Skip to main content

Command Palette

Search for a command to run...

Strategy

Updated
2 min read

With strategy design pattern, it is possible do define a family of algorithms and choose the appropriate one to use at runtime. Since the algorithm is implemented in a separate class, it makes them interchangeable.

Strategy design pattern uses composition approach.

It separates the business logic of a class from the implementation details of algorithms (SRP).

Adding a new algorithm won’t affect the client code (Open/Close Principle).

Structure

Implementation

Classic Example (Dynamic)

// Define the interface common to all supported versions of some algorithms
class Strategy {
public:
    virtual ~Strategy() = default;
    virtual std::string doAlgorithm(std::string_view data) const = 0;
}

class ConcreteStrategyA : public Strategy {
public:
    std::string doAlgorithm(std::string_view data) const override
    {
        std::string result(data);
        std::sort(std::begin(result), std::end(result));
        return result;
    }
}

class ConcreteStrategyB : public Strategy {
public:
    std::string doAlgorithm(std::string_view data) const override
    {
        std::string result(data);
        std::sort(std::begin(result), std::end(result), std::greater<>());
        return result;
    }
}

class Context
{
private:
    std::unique_ptr<Strategy> m_strategy;

public:
    explicit Context(std::unique_ptr<Strategy>&& strategy = {}): strategy_(std::move(strategy)) {}

    // ALternatively use factory pattern
    void set_strategy(std::unique_ptr<Strategy>&& strategy) {
        m_strategy = std::move(strategy);
    }

    void doSomething() const {
        if (m_strategy) {
            std::string result = m_strategy->doAlgorithm("abcde");
        } else {
            std::cout << "Context Strategy is not set
";
        }
    }
}

int main() {
    Context context(std::make_unique<ConcreteStrategyA>());
    context.doSomething();
}

Static Strategy (Template)

If you want to avoid using vtable.

template<typename LS>
struct TextProcessor {
    void append_list(const vector<string> &items) {
        m_list_strategy.start(m_oss);
        for (auto & item: items)
            m_list_strategy.add_list_item(m_oss, item);
        m_list_strategy.end(m_oss);
    }

    string str() const { return m_oss.str(); }
private:
    ostringstream       m_oss;
    LS                  m_list_strategy;
};

int main() {
    // markdown
    TextProcessor<MarkdownListStrategy> tp1;
    tp1.append_list({ "foo", "bar", "baz" });
    cout << tp1.str() << endl;

    // html
    TextProcessor<HtmlListStrategy> tp2;
    tp2.append_list({ "foo", "bar", "baz" });
    cout << tp2.str() << endl;

    return EXIT_SUCCESS;
}

Functional approach using Lambda

The strategy can be implemented as an anonymous function instead. With this approach, we can avoid bloating the code with extra classes and interfaces

Credits

https://refactoring.guru/design-patterns/strategy

https://vishalchovatiya.com/posts//strategy-design-pattern-in-modern-cpp/

19 views