The Strategy pattern is one of the simpler design patterns and probably a good one to get started with design patterns. Additionally, it is also very practical and can help to clean up the code.
- encapsulate related algorithms so they are callable through a common interface
- let the algorithm vary from the class using it
- allow a class to maintain a single purpose
The most obvious sign that you might want to use the strategy pattern are switch statements. In my example further down, I will also show how a switch statement can be replaced by the Strategy pattern.
Another hint that you should use the Strategy pattern is when you want to add a new calculation but in doing so you have to modify your class which violates the open-closed principle.
The context class does the work. It takes the desired strategy in the constructor (the strategy can also be passed as parameter in the method as you will see later). The strategy interface declares a method which is called by the context class to perform the calculation I want on a concrete strategy.
There are many examples of implementing the Strategy pattern on the internet. Popular ones are implementing different search algorithm or different calculations for products or orders. In my example, I will implement a cost calculator for products. The costs depend on the country in which the product is produced.
My products have some basics properties like price or name and also the production country. The ProductionCostCalculatorService class implements a calculate Methode in which it has a switch Statement. Depending on the production Country, the production costs are calculated differently.
The production countries are China, Australia and the USA. If a different production Country is passed in the product, a UnknownProductionCountryException is thrown.
This approach has some flaws. The biggest problem is that it is not easy to add a new country. To achieve that, a new case has to be added to the switch. This doesn’t sound like a problem but it violates the open-closed principle and is also a problem if you have to add a new country every week. You will end up with a huge switch Statement. Another design flaw is that the product doesn’t have to know where it is produced. Therefore the algorithm shouldn’t rely on the information from the product.
You can find the source code on GitHub. The solution to the problems is the Strategy pattern.
To implement the strategy pattern, I have to implement a new class for every strategy. As a result, I will have the classes ChinaProductionCostStrategy, AustraliaProductionCostStrategy and UsaProductionCostStrategy. These classes implement the new Interface IProductionCostCalculatorService which has one method. The method is Calculate and takes a product as parameter.
After these changes, I can modify the ProductionCostCalculatorService class. First, I inject the IProdctionCostCalculatorService in the constructor. Then, I change the CalculateProductionCost method to return the return value of the Calculate method from the Interface.
The last step is to modify the call of the calculation. For every strategy, I need an object, which I inject into the constructor of the ProductionCostCalculatorService.
I have to admit that These calls look a bit messy and I am not a big fan of them. Therefore it is possible to change the ProductionCostCalculatorService, so it takes the strategy as parameter in the Calculate method instead of the constructor.
This change also tidies up the ProductionCostCalculatorService, which makes it even easier to read. With this changes implemented, I can now change the call of the calculation. It is not necessary anymore to create a new ProductionCostCalculatorService object for every different production country. Instead, I pass the country as parameter in the CalculateProductionCost method.
You can find the source code on GitHub.
In this example, I showed that the Strategy pattern is a simple pattern which helps to tidy up our code. While programming, look out for switch statements since these often indicate that the Strategy pattern could be used. As a result of the Strategy pattern, decoupling between the algorithm and other classes can be achieved.
Additionally, I showed a second variation on how to implement the pattern which leads to an even cleaner implementation.