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
}
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 }
};
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
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; }
}
Great, now create a shape.
public class Circle: IShape
{
public string Color { get; set; }
public string Shade { get; set; }
}
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";
}
}
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);
}
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
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();
}
}
}
Create and colour the circle like this,
var circle = new Circle();
var coloring = ColoringStrategy.Coloring(ShadeColor.DarkOrange);
coloring.ColorAndShade(circle);
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)