Design Patterns: Dynamic behavior with the Strategy pattern
This is the second instalment of my series on design patterns in software. For the introduction to the whole series, definitely check out Design Patterns: Introduction with Singletons. This initial post describes general information about design patterns as well. The Strategy pattern is a little bit more complex than the Singleton pattern, but still not too crazy!
The strategy pattern is one of those patterns that help you describe behaviour, but keep it loosely coupled to the class that requires that behaviour. It is a behavioural pattern.
Its purpose is making it easier and more clear to dynamically inject behaviour (a 'strategy') into a class. The behaviour should implement an interface and because of that, the class receiving the 'strategy' can simply talk to that interface and not ever worry about the implementation. This means that you can (even at run time) change the behaviour of a class, for example a game character, a product entity or any other odd component in software.
The strategy pattern:
- Defines a family of algorithms.
- Encapsulates each algorithm in the family.
- Makes the algorithms interchangeable within that family.
Before getting into depth with the open/closed principle, examples and code - here's a class diagram displaying a very basic strategy pattern:
Quite simply, you're applying the open/closed principle. More on that in a minute, but in essence, the strategy pattern allows for easy modulation and maintainability of code with dynamic behaviour. Many programmers have all had the experience in their life that at a certain point, a customer (or a system) required additional behaviour. This behaviour only applied in certain cases, but had the same parameters as the behaviour that already existed.
You could then simply add another function somewhere, or inject a new method in your class. You could use if statements (I've seen a lot of code with a long... long... long list of statements, switches, loops) or you could use the strategy pattern.
Exchangable algorithms and abstraction
The diagram above displays a real world example. In this case, there is a MyArrayList which allows for small or large data sets to be added. After the addition, it could sort automatically, or you could call Sort(). The sorting algorithm must be dynamic and in the future, more sorting algorithms will be added.
For example at one moment at runtime a small data set will be sorted. This could be done by creating a
MyArrayList arr = new MyArrayList() and then adding the strategy by calling
arr.SetSortingAlgorithm(new InsertionSort()) because insertion sort is fast enough for small sets. Other larger data sets might require a more optimized or elegant algorithm, then you'd call
arr.SetSortingAlgorithm(new MergeSort()). This process could be automated based on the number of items being added to the list.
MyArrayList never knows what sorting algorithm is being used, because it simply doesn't care. It isn't responsible for that any more, it just needs to know that there is a strategy or behaviour defined for sorting the data. It doesn't matter how the strategy attacks the problem in the end. It knows how to communicate with the strategy because of the interface, which is enough.
By using the strategy pattern, adding new algorithms to the family of algorithms is easy. You simply add a class that implements the interface that all algorithms in the family implement. If you look at the example above again, often you don't even want sorting, so you could make a NoSort strategy which simply does nothing at all in that case (or omit setting a strategy).
If you look at the class diagram, you see simply adding a class that implements ISortBehaviour will suffice in order to extend the SortBehaviour family. No if statements, no conditions, just a new class and a call to SetSortingAlgorithm.
Yeah yeah, but this open/closed principle?
Here's what Wikipedia has to say on the topic:
In object-oriented programming, the open/closed principle states "software entities (classes, modules, functions, etc.) should be open for extension, but closed for modification"; that is, such an entity can allow its behaviour to be extended without modifying its source code.
There are different points of view on the open/closed principle, but the basic idea is clear; code should be open for extension and closed for modification. In the strategy pattern, that means that the behaviours of a class should not be inherited, but encapsulated elsewhere. An interface is then required to enable other entities in the package to communicate with those behaviours.
This way, the open/closed principle and the strategy pattern make sure the behaviour can easily be extended, without really needing to modify existing code anywhere else than the code requiring the behaviour. To prevent compatibility issues and broken packages, one should only add new modules intead of modifying existing code.
Example in TypeScript
Here's a simple example in TypeScript that animates two little boxes. It is based on the example from the Design Patterns: Introduction with Singletons post. It now includes the strategy pattern for the movement of the squares. The idea is that it should not matter what movement strategy is used and a new strategy can be easily added.
Check out this pen.
I hope this was a good read, but hoping doesn't get me far. I love feedback, so let me know if I need to change anything or adjust the format. I want this to be as educational and understandable as it can be! I'll write about the Observer pattern next, which is a little stalker pattern!