We all tend to use if-else and switch-case whenever we have some condition to check against. And that’s fine, until it’s not.

Let’s look at the simple example below.

enum Weapon
{
    Axe,
    Sword,
    Spear
}

class Character
{
    public void Attack(Weapon weapon)
    {
        if (weapon is Weapon.Axe)
            Console.WriteLine("Attack with Axe");
        else if (weapon is Weapon.Sword)
            Console.WriteLine("Attack with Sword");
        else if (weapon is Weapon.Spear)
            Console.WriteLine("Attack with Spear");
        else
            throw new ArgumentOutOfRangeException("Invalid weapon");
    }
}

And here is how we instantiate the Character above and Attack.

var character = new Character();
character.Attack(Weapon.Spear);

It’s simple and it works, right? However, there is a problem with the design here that could lead to trouble & maintenance issues down the road.

The problem is that the code above is not compliant with the Open-Closed principle which states that a class should be open for extension but closed for modification.

Every time we need to add another type of weapon or change an existing one we need to modify the Character class, which is bad as the Character class is already developed, tested and shipped. Any modifications could potentially jeopardize it. So let’s use the Strategy pattern to fix the design.

First, here is the UML diagram of the pattern.

To comply with the diagram we’d need a Strategy and a few Concrete strategies. The Strategy in our example will be the Weapon and the concrete strategies will be the different implementations (sword, axe, spear and etc…). Let’s see how below.

First, let’s create a Strategy interface, called IWeapon.

public interface IWeapon
{
    void Attack();
}

Then let’s create a few concrete implementations – for axe, sword and spear weapons.

class Sword : IWeapon
{
    public void Attack()
    {
        Console.WriteLine("Attacking with a sword");
    }
}
class Axe : IWeapon
{
    public void Attack()
    {
        Console.WriteLine("Attacking with an Axe");
    }
}
class Spear : IWeapon
{
    public void Attack()
    {
        Console.WriteLine("Attacking with a Spear");
    }
}

Now, let’s change the Attack method a bit.

class Character
{
    public void Attack(IWeapon weapon)
    {
        weapon.Attack();
    }
}

Look how simple it is now, right? No nasty if-else statements. Here, through polymorphism, we will inject the correct strategy (weapon) to be used.

Character ch = new Character();
ch.Attack(new Sword());
ch.Attack(new Axe());
ch.Attack(new Spear());

Awesome!

Next time we need to add another weapon, let’s say a Bow, the only think we need to do is create a strategy for it and inject it, like so:

class Bow : IWeapon
{
    public void Attack()
    {
        Console.WriteLine("Attacking with a Bow");
    }
}
Character ch = new Character();
ch.Attack(new Bow());

There is no need to add / modify if statements any more. Now the code is OCP compliant and it doesn’t care what kind of weapon you will decide to add tomorrow, as long it is an IWeapon the code would work without any modifications.

So that, in a nutshell, is the Strategy Pattern – very powerful and easy to use pattern that helps with decoupling your code, making it OCP compliant and easy to maintain.

Hope you found this short article useful 🙂

Categorized in:

Design Patterns,

Last Update: August 2, 2022