March 11, 2021

Strategy pattern

The strategy pattern

A simple yet effective pattern to control what behavior to execute.

We’ll start with code and then I want to empathize the importance of this pattern.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
using System.Collections.Generic;

namespace Donuts
{

public class Donut
{
public int SweetnessLevel { get; protected set; }
}

public class ChocolateDonut : Donut
{
public ChocolateDonut() { SweetnessLevel = 6; }
}
public class BakedJellyDonut : Donut
{
public BakedJellyDonut() { SweetnessLevel = 4; }
}

public interface IDonutMaker
{
Donut Make();
}
public class ChocolateDonutMaker : IDonutMaker
{
public Donut Make()
{
// .. Making the donut
return new ChocolateDonut();
}
}
public class BakedJellyDonutMaker : IDonutMaker
{
public Donut Make()
{
// .. Making the donut
return new BakedJellyDonut();
}
}

public class Program
{
public static void Main(string[] args)
{
// Dictionary is like a HashMap in Java or an std::map in C++.
IDictionary<string, IDonutMaker> donutMakers = new Dictionary<string, IDonutMaker>()
{
{ "Chocolate" , new ChocolateDonutMaker() },
{ "BakedJelly", new BakedJellyDonutMaker() }
};

var donutRecipe = args[0];
var donut = donutMakers[donutRecipe].Make();

// Give customer donut
}
}
}

The IDonutMaker interface

We’ve introduced an interface and not a concrete class.
This is the first important lesson - Code to interfaces and not classes.

Actually CSharps interface is the implementation of the strategy pattern.
Therefore it’s the easier choice for creating an abstracted logic.

In C++ I prefer to create abstract classes or virtual classes to create a base logic.
C# has the keyword ‘interface’ therefore it makes a convention of naming them “IXxxYyy”.
In C++ I prefer to abandon it as it makes less sense although it’s based on preferences and company policies.

1
2
3
4
5
6
7
8
9
struct Donut 
{
};

class DonutMaker
{
public:
virtual Donut Make() = 0;
};

The Strategy pattern - A composable method to choose behavior on the fly

The pattern allows one to change the behavior very quickly based on choices or other conditions.
Usually it works well with a dictionay such as the C# Code creates.
In this example - based on the recipe we choose the appropriate donut maker behavior.

This pattern allows us to compose our classes in a decoupled way.
Composition is better than inheritance because we can remove or add additional behavior based on what we need.
If we try to make more inheritance hirarchies it creates more mess than we intended:

More donut makers will bloat this class into a god-class where it must implement all of the logic.
It’s bug prone and leads to very messy code.

Bad code:

1
2
3
class DonutsMaker : public ChocolateDonutMaker , public BakedJellyDonutMaker,
public BananaDonutMaker, public PeanutButterDonutMaker // ... and on and on
{ /* Omitted for clarity */ };

Diagram

When speaking of an abstract pattern we could visuallize it as follow:

A consumer that needs an abstracted logic can be composed of the interface.
This is equivilant to holding a pointer or a reference to the interface (Depending on what language you’re programming in).

How the consumer gets the concrete implementation is another topic, just to mention the different ways:

A solid pattern

Explaining SOLID is yet another topic so I’ll refrain from it in here.
The strategy pattern is a safe way to implement a solid-based code.

It encourages us to create more meaningful interfaces with much lower coupeling and higher cohesion.
Also we depend on the interface and not the concrete implementations making the code easier to extend and maintain.

Testable

The pattern encourages us to split different behaviors to different implementations, making it easier to unit test individual classes.

This is C# unit test code written using NUnit framework.

1
2
3
4
5
6
7
8
9
10
11
 [Test]
public void ChocolateDonutMaker_Make_MakesChocolateSweet()
{
const int MinimalSweetnessLevel = 5;

ChocolateDonutMaker maker = new ChocolateDonutMaker();
var donut = maker.make();

// We want chocolate donuts to be sweet!
Assert.That(donut.SweetnessLevel, Is.GreaterThan(MinimalSweetnessLevel));
}

The strategy pattern is very basic yet knowing all of the avdantages will help us be more conscious about what kind of code we’re writing, and in the end will help us produce higher quality code.

Thanks for reading!

על הפוסט

הפוסט נכתב על ידי Ilya, רישיון על ידי CC BY-NC-ND 4.0.

שתפו את הפוסט

Email Facebook Linkedin Print

קנו לי קפה

#Code#Programming#Softwar#Pattern#Strategy