Sandrino's WEBSITE

   ABOUT  BLOG  


The Strategy Pattern

What is the Strategy Pattern

It is a behavioural design pattern, that allows you to define a family of algorithms, encapsulate each one and make them interchangeable. This is a nice formal description of the design pattern but let me try to explain it with a real life analogy. We could imagine we have a RPG game and we have a entity Character and this character has to go into a battle and the user can define the strategy it can either be a warrior (specialised in closed combat heavy armor and at the frontline), then we have the mage (specialised in spell casting and magical abilities, keeping a safe distance to the enemy) and a assassin (specialised in stealth, critical damage and precise attacks). But additionally each of those strategies has to decide between several options for example mage can have either a strategy for fire or water. Warrior can have one hand or two hand strategies and also the assassin with crossbow or dagger.

The character strategy(warrior, mage, assassin) defines the strategy with which the player will go into the battle. The player can dynamically choose with what he will try to attempt to win the battle.

Naive way to model

So no lets try to model this example from above in a naive approach which to be honest I would have done before. So here is a little diagram how this could look like:

Diagram 1

As we can see there is already some nested inheritance and I only added the mage, we can imagine how this diagram would look if we add also for the warrior and assassin all sub classes and all of them have a FightStrategy we have to take care of. Often they only differentiate in some small details.

This maybe seems okay for now but lets say after some time we want to add a additional function to the character called winningAnimation() this would lead to huge amount of work we would have to implement new classes which mirror this functionality and probably it is often the same implementation. Just to visually show the new diagram with the winningAnimation:

Diagram 1

As we can see the WinningAnnimation function has to be implemented in two different places and it most probably will look very similar.

The Problem

Now what can we identify as problems:

DRY (Don't repeat yourself)

DRY advises us to not repeat code. If its written once it should be enough for all cases. The winningAnimation function would be similar in all mage classes but we would have to implement it for each separately. Also if we have to change one implementation there is a chance that we forget to change it on the other place then we run into inconsistency.

OCP (Open-Closed Principle)

We can expect code to change so imagine we add a third function. We can already see what that would mean and for sure it means no fun. The goal with OCP is to simplify for extension. It must be possible if we want to add a new functionality to only add new code and not modify existing code.

SRP (Single Responsibility Principle)

"Everything should do just one thing" Or better said:

,,The single responsibility principle advises to separate concernes to isolate and simplify change''

- Klaus Iglberger

Solution with Strategy Pattern

It is important to remember to not directly apply inheritance to the MageCharacter class for example I will use the MageCharacter in the following sentences but it should stand for or types of "Character Fight Strategies".

We can now identify that FightStrategy changes for each concrete class. So this is a hint that we could extract this information to follow the SRP. Also the character itself does not need to know exactly which strategy it uses it is only important to delegate this task. Additionally with the extraction of FightStrategy we make it much easier to obey the OCP.

In diagram form: Diagram 1

We only focus on the MageCharacter. As we can see the MageCharacter class is delegating its strategy to the FightStrategy class. As we already said it does not need to know the exact strategy or the implementation this is part of the FightStrategy class its only responsibility is to take care of the FightStrategy. Also if we need an additional strategy we just create a new class derived from FightStrategy and we are good to go. Also now its much easier for the MageCharacter to change its Strategy before there was no way to do so. It is possible now because the MageCharacter will hold a unique_ptr of type FightStrategy and with a setter function we can always change that.

Implementation / C++

#include <iostream>
#include <memory>
#include <string>
#include <vector>

//-----------------------------------------------------------------------------
class Character
{
  public:
    Character()          = default;
    virtual ~Character() = default;

    virtual void fightStrategy( /*...*/ ) const = 0;
    /* virtual void winningAnimation(  ) const = 0; */
};

//-----------------------------------------------------------------------------
class MageCharacter;

//-----------------------------------------------------------------------------
class MageFightStrategy
{
  public:
    virtual ~MageFightStrategy() {}

    virtual void fighting( const MageCharacter& mage /*...*/ ) const = 0;
};

//-----------------------------------------------------------------------------
class MageCharacter : public Character
{
  public:
    MageCharacter(
        std::string                        name,
        std::unique_ptr<MageFightStrategy> strategy )   // Dependency injection
        : charName{ name }, fight{ std::move( strategy ) }
    {
    }

    virtual void fightStrategy( /*...*/ ) const override
    {
        fight->fighting( *this /*...*/ );   // Delegating to strategy class
    }

    /* virtual void winningAnimation() const override; */

  private:
    std::string                        charName;
    std::unique_ptr<MageFightStrategy> fight;
};

//-----------------------------------------------------------------------------
class WarriorCharacter;

//-----------------------------------------------------------------------------
class WarriorFightStrategy
{
  public:
    virtual ~WarriorFightStrategy() {}

    virtual void fighting( const WarriorCharacter& warrior /*...*/ ) const = 0;
};

//-----------------------------------------------------------------------------
class WarriorCharacter : public Character
{
  public:
    WarriorCharacter( std::string name,
                      std::unique_ptr<WarriorFightStrategy>
                          strategy )   // Dependency injection
        : charName{ name }, fight{ std::move( strategy ) }
    {
    }

    virtual void fightStrategy( /*...*/ ) const override
    {
        fight->fighting( *this /*...*/ );
    }

    /* virtual void winningAnimation() const override; */

  private:
    std::string                           charName;
    std::unique_ptr<WarriorFightStrategy> fight;
};

//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
// Concrete implementation of Strategies
class MageFireStrategy : public MageFightStrategy
{
  public:
    virtual ~MageFireStrategy() {}

    virtual void fighting( const MageCharacter& mage /*...*/ ) const override
    {
        std::cout << "Mage fire strategy" << std::endl;
    }
};

//-----------------------------------------------------------------------------
class MageWaterStrategy : public MageFightStrategy
{
  public:
    virtual ~MageWaterStrategy() {}

    virtual void fighting( const MageCharacter& mage /*...*/ ) const override
    {
        std::cout << "Mage water strategy" << std::endl;
    }
};

//-----------------------------------------------------------------------------
class WarriorOneHandStrategy : public WarriorFightStrategy
{
  public:
    virtual ~WarriorOneHandStrategy() {}

    virtual void
    fighting( const WarriorCharacter& warrior /*...*/ ) const override
    {
        std::cout << "Warrior one hand strategy" << std::endl;
    }
};

//-----------------------------------------------------------------------------
class WarriorTwoHandStrategy : public WarriorFightStrategy
{
  public:
    virtual ~WarriorTwoHandStrategy() {}

    virtual void
    fighting( const WarriorCharacter& warrior /*...*/ ) const override
    {
        std::cout << "Warrior two hand strategy" << std::endl;
    }
};

//-----------------------------------------------------------------------------
int main()
{
    using Characters = std::vector<std::unique_ptr<Character>>;

    Characters characters;
    characters.emplace_back( std::make_unique<MageCharacter>(
        "Sandi", std::make_unique<MageFireStrategy>() ) );

    characters.emplace_back( std::make_unique<MageCharacter>(
        "Moji", std::make_unique<MageWaterStrategy>() ) );

    characters.emplace_back( std::make_unique<WarriorCharacter>(
        "Domi", std::make_unique<WarriorOneHandStrategy>() ) );

    characters.emplace_back( std::make_unique<WarriorCharacter>(
        "Michi", std::make_unique<WarriorTwoHandStrategy>() ) );

    for ( const auto& ch : characters )
    {
        ch->fightStrategy();
    }
}
Output:
Mage fire strategy
Mage water strategy
Warrior one hand strategy
Warrior two hand strategy

As we can see the Character class now only defines what a character should do (as a interface should do anyway) and the concrete classes (MageCharacter, WarriorCharacter) only delegate the call to the strategy. It is basically a wrapper calling the function of the strategy.

The strategy itself can be changed during runtime with a simple setter. We also reduced the hierarchy depth.

Summary

The Strategy Pattern is a behavioral design pattern that allows for the encapsulation of algorithms into interchangeable components. In a relatable analogy with RPG characters, we see how different character classes represent varying strategies in combat, such as warriors, mages, and assassins. However, a naive approach to modeling these characters leads to problems like deep inheritance hierarchies and code duplication.

By employing the Strategy Pattern, we can address these issues by separating the character's behavior into distinct strategies, such as fight strategies for different character types. This approach adheres to principles like the Single Responsibility Principle (SRP), ensuring each class has a single responsibility, and the Open-Closed Principle (OCP), enabling easy extension without modifying existing code.

In C++, we demonstrate the implementation of the Strategy Pattern, where character classes delegate their behavior to strategy classes, promoting code reuse and flexibility. With this pattern, we simplify maintenance, reduce hierarchy depth, and enhance code extensibility, providing a robust solution for managing diverse behaviors in software systems.

Sources

https://refactoring.guru/design-patterns/strategy Klaus Iglberger CppCon