DEV Community

Cover image for Let's talk Strategy Pattern
Henrick Tissink
Henrick Tissink

Posted on • Edited on

Let's talk Strategy Pattern

Porygon

Porygon is a Normal-type Pokemon created entirely from computer code. It can transform itself into code to travel across cyberspace. It has two signature moves, Conversion and Conversion 2. These transform Porygon's type when it's in a Pokemon battle. So Porygon is some code that changes it's type (or execution) during a battle (at run-time). This in essence is the Strategy Pattern.

Dynamic Dispatch

The Strategy Pattern is built on this nifty little thing called dynamic dispatch. Dynamic dispatch is when a program decides what code to execute at run time. The opposite of dynamic dispatch is static dispatch. This is when a program knows what code it's going to execute at compile time.

Being Functional

This pattern forms a fundamental part of functional programming - the ability to create and use families of functions at run time is essential to writing good functional code. A family of functions share the same type-signature - which means that they take the same parameters, and return the same type.

There are many different variations of the Strategy Pattern. The two main implementations will be discussed.

The first is my favourite. It's the neatest with the smallest amount of code. For this we'll be building a simple calculator.

It's Calculated

First, let's define the functionality.

public enum Operation
{
    Addition,
    Subtraction,
    Division,
    Multiplication
}
Enter fullscreen mode Exit fullscreen mode

Great, now that the functionality has been defined a rad little bit of functionality will be cooked up using the Func delegate and a Dictionary.

var Operations = new Dictionary<Operation, Func<int, int, int>>
{
    { Operation.Addition, (x, y) => x + y },
    { Operation.Subtraction, (x, y) => x - y },
    { Operation.Division, (x, y) => x / y },
    { Operation.Multiplication, (x, y) => x * y }
};
Enter fullscreen mode Exit fullscreen mode

At run time, the program (or user) can decide which of the operations to call. Each of the functions belong to the same family, having the type-signature (int, int) -> int. A dictionary of functions gives a sexy bit of syntactic sugar, using it to add two numbers (at run time) as follows:

var result = Operations[Operation.Addition](1, 2) // result = 3
Enter fullscreen mode Exit fullscreen mode

More formally, these functions can be called at run time based on some user selected operation, and allow the addition and removal of functionality based on what's in the dictionary.

Colour Me Crazy

Next, we'll have a look at the Strategy Pattern implemented using interfaces. For this we'll be making a simple shape drawing app. Assuming the app can already draw the shapes, the shapes will be shaded and coloured using the Strategy Pattern. First, create an interface for the shapes.

public interface IShape
{
    string Color { get; set; }
    string Shade { get; set; }
}
Enter fullscreen mode Exit fullscreen mode

Great, now create a shape.

public class Circle: IShape
{
    public string Color { get; set; }
    public string Shade { get; set; }
}
Enter fullscreen mode Exit fullscreen mode

Cooking with Grease! Now lets focus on the strategy - the colouring and shading.

public interface IColoring
{
    void ColorAndShade(IShape shape);
}

public class LightGreen : IColoring
{
    public void ColorAndShade(IShape shape)
    {
        shape.Color = "Green";
        shape.Shade = "Light";
    }
}

public class DarkOrange : IColoring
{
    public void ColorAndShade(IShape shape)
    {
        shape.Color = "Orange";
        shape.Shade = "Dark";
    }
}

public class MediumBlue : IColoring
{
    public void ColorAndShade(IShape shape)
    {
        shape.Color = "Blue";
        shape.Shade = "Medium";
    }
}
Enter fullscreen mode Exit fullscreen mode

So we now have a strategy for shading and colouring, and we have three colours and shades. There's two ways we can go about this.

1. Strategy Property

Modifying the shape class a little.

public interface IShape
{
    string Color { get; set; }
    string Shade { get; set; }
    IColoring Filling { get; set; }
    void Fill();
}

public class Circle: IShape
{
    public string Color { get; set; }
    public string Shade { get; set; }
    public IColoring Filling { get; set; }
    public void Fill() => Filling?.ColorAndShade(this);
}
Enter fullscreen mode Exit fullscreen mode

Now, creating a circle, it can be coloured and shaded at run-time doing

var circle = new Circle();
circle.Filling = new DarkOrange();
circle.Fill(); // Circle is new Dark Orange
Enter fullscreen mode Exit fullscreen mode

2. Strategy Factory

public enum ShadeColor
{
    LightGreen,
    DarkOrange,
    MediumBlue
}

public static class ColoringStrategy
{
    public static IColoring Coloring(ShadeColor coloring)
    {
        switch (coloring)
        {
            case ShadeColor.DarkOrange:
                return new DarkOrange();
            case ShadeColor.LightGreen:
                return new LightGreen();
            case ShadeColor.MediumBlue:
                return new MediumBlue();
            default:
                throw new NotImplementedException();
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Create and colour the circle like this,

var circle = new Circle();
var coloring = ColoringStrategy.Coloring(ShadeColor.DarkOrange);
coloring.ColorAndShade(circle);
Enter fullscreen mode Exit fullscreen mode

In Closing

This is a trivial example but it illustrates the point easily. Similar implementations can be used to select the right sorting algorithm, or to filter out different objects to modify. Strategies can be used in a host of different ways that fit the need for dynamic dispatch.

Strategies are rad and they help decouple the complexities of your executing code from your calling code. In the first case all the code cared about was the type-signature, in the second case it was the interface. In both cases the code was decoupled from the actual execution, resulting in cleaner more maintainable code.

Top comments (0)