Create your next strategy
React is already a factual proof that backs that up as amazing tools were invented thereafter. There is also Electron which powers today’s booming technology like Visual Studio Code and Figma.
In this post, we will be going over the Strategy Design Pattern. This is a well known pattern that encapsulates one or more strategies (or algorithms) to do a task. These encapsulated strategies all have the same signature so the context (the one who provides the interface) never knows when they are dealing with the same or different object (or strategy). This means that each strategy can be swapped together many times without our program ever realizing it during the lifetime of our app.
In the strategy pattern, these two objects are always involved:
The Context must always have a reference or pointer to the current strategy being used. That means if we have 200 strategies then it’s optional that the other 199 are used. You can think of them as being “inactive”.
The Context also provides the interface to the caller. The caller is the client. The caller can use any of the strategies to perform their work and they can also switch the current strategy with another strategy at any time on demand.
The actual Strategy implements the execution logic for itself that will be used when executed.
In a normal function implementation, the function is usually doing something and returns a value. In the Strategy Design Pattern when there is a base (Context) class and one Strategy it is like a function that calls the Strategy and returns the result (in other words the same thing).
But when there are two or more strategies, the point is that the strategy can be one of many strategies controlled by the caller.
The major benefit here is that we can define as many strategies as we want and swap between each one to be used on-demand without inflicting a single hint of change in behavior of code as long as the pattern is written the way it should.
Implementations of a Strategy can change but as long as they keep the same signature as expected by the context then there is no need to experience unnecessary changes to code.
Here is a diagram depicting this flow:
Our first implementation will focus on fetching. We’ll define a
createFetcher function that returns the interface to create fetchers. These fetchers can be spawned by the client and can be implemented however they desire as long as they take in a URL, retrieve, and returns its response.
We’ll be using the axios request library, node’s native https module and the node-fetch library to implement one strategy each.
In total we will have 3 strategies:
createFetcher function we created this line:
const _identifer = Symbol('_createFetcher_')
This line is important because we want to ensure that each strategy created is actually a strategy otherwise our program will treat any passed in object as a strategy. It may sound like a positive benefit to have anything treated as a strategy but we would lose validity which makes our code more prone to errors which can easily deter our debugging experience if we misstep.
Symbol returns to us a unique variable by definition. It is also hidden within the implementation of the contextso there is no way that objects created outside of our
create function will be treated as a strategy. They would have to use the method made publicly from the interface provided by the context.
When the client calls
use it’s submitting
axiosFetcher to be the used as the current strategy and is then bound as a reference until the client swaps in another strategy via
Now we have three strategies for retrieving data:
Hurray! We’ve now seen how it can be implemented in code. But can we think of a situation in the real world where we need this? You can think of plenty actually! However if this is your first time reading about this pattern then I understand that it can be hard to think of a scenario beforehand unless we see one in practice first.
The examples we went over in this post shows the pattern implementation but anyone reading this can ask “Why bother implementing three fetcher strategies when you can just directly use one like axios to get the response and call it a day?”
In the upcoming example we will be going over a scenario where the Strategy Design Pattern is definitely needed.
Where the strategy pattern shines most is when we need to handle different data types when doing something like sorting.
In the previous examples we didn’t really care about any data types because we just wanted some response. But what happens when we receive a collection of something and needed to do some narrow task like categorizing them? What if they need to be sorted correctly?
When we need to sort several collections where each are a collection of another data type we can’t just use the native
.sort method on all of them because each value can be treated differently in terms of “less” and “greater”.
We can use the Strategy Pattern and define different sets of sorting algorithms that are readily available in the runtime so that we can use them interchangeably on demand.
Consider these collections:
We can create a
Sort strategy class and a
Sorter context class.
Note that they don’t need to be classes. We’re just choosing to use classes now to diversify the implementation a little:
It’s pretty straight forward.
Sorter keeps a reference to the
Sort that is currently being used. This is the sort function that will be picked up when calling
Sort instance is a strategy and passed into
Sorter does not know anything about the strategies. It does not know that there is a date sorter, number sorter, etc. It just calls the Sort’s execute method.
However the client knows about all of the
Sort instances and controls the strategies as well as the
With that said, its entirely up to us (the client) to handle this accordingly:
We now have a robust 15 line function that can sort 4 different variations of collections!