Design patterns are solutions for typical and recurring problems that we may encounter when developing an application. In this article, is focus on the Strategy Pattern.
What is the Strategy Pattern?
The strategy pattern defines a family of algorithms, encapsulate each one as an object, and make them interchangeable independently of the clients that use them.
This pattern allows us to add new functionality without refactoring the current code by adding or eliminating behaviours according to the business needs. Additionally, this pattern helps to achieve two of the SOLID principle:
Single Responsibility: “A class should have only reason to be changed”.
Open-Closed: “Software entities (classes, modules, functions.) should be open for extension, but closed for modification.”
When can the strategy pattern be applied?
Let’s see with an example of when the Strategy Pattern can be applied. Imagine that we want developing an app that prints the book’s title in a different format given an input.
For example, the title of the book is “Harry Potter”, and these are the possible format that our app supports:
- Print the first character of the title is in lowercase: Eg: “harry Potter.”
- Print the title in upper case: Eg: “HARRY POTTER.”
- Print the title in lower case. E.g., “harry potter.”
There are many different ways of solving this problem; The following code is the most obvious one to tackle it.
The Book class represents a book. It has a title property and “formatTitle” function. This function is in charge of formatting the title of the book depending on the format given as a parameter. To do that, “formatTitle” compares the format given by the user with the format that the app supports and then it makes the specific transformation.
So far everything is working fine; the app is behaving as expected.
Now, Imagine that there is a new requirement. A client wants the apps to support another format:
- The first character of the title has to be uppercase. E.g., “Harry potter”
The simplest way to achieve this new requirement is to add another condition inside the switch statement.
What are the problems with the above design?
The problem with that is it breaks the Open Closed Principle due to the fact that we have to update the “formatTitle” function to introduce the new requirement every time that a new format is needed.
Switch statements are not easily testable due to you would have to cover each execution paths, and every condition would require you to write at least one more test. On the other hand, classes which implement strategies typically expose just one public method, which is easy to test.
How can we solve this problem?
This scenario is the perfect case to apply the Strategy Pattern.
We can isolate each of the uses cases in different classes called strategies that define an interface common to all supported algorithms. Then the Context class uses this interface to execute the algorithm implemented by a Concrete Strategy. In that way, the context is independent of the strategy. We can add a new algorithm or update the existing one without changing the Context class or the strategy.
Now that that the Strategy Pattern is clear let’s have a look to the code.
- Firstly, separate each transformation into different classes where each class represent a different strategy. Each strategy has to implement the interface that defines the action, in this example is `formatTitle`.
2. Secondly, each strategy is executed for the Context. Context is in charge of delegating the work to a strategy object, according to the type of format we receive.
Our main program looks like this:
At the end of the execution, you’ll have something like this:
You can see the code complete here.
The main benefits of this patters are:
- The code is easy to maintain. Now it is much easy to add more functionality.
- The code is easy to read and test it.
- We achieved the Open-Closed principle