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 | using System.Collections.Generic; |
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 | struct Donut |
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:
- C# Doesn’t allow multiple class inheritance (as of C# 9 it allows with interfaces), therefore I implemented it using C++.
1
2
3
4
5
6class ChocolateDonutMaker : public DonutMaker{ /* omitted for clarity */ };
class BakedJellyDonutMaker : public DonutMaker { /* omitted for clarity*/ };
class DonutsMaker : public ChocolateDonutMaker , public BakedJellyDonutMaker
{ /* Omitted for clarity */ };
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 | class DonutsMaker : public ChocolateDonutMaker , public BakedJellyDonutMaker, |
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:
- Injecting through ctor, property or method.
- Creating and holding the instances as a list or a dictionary inside the class.
- Abstracting this behavior into another class -> This usually is an overdesign unless you have too many different strategies.
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 | [ ] |
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!