In last post on design patterns, we learned about builder pattern, in this post we will be discussing strategy pattern. Before going into details, first understand basic intent of strategy pattern. Intent is to define a family of algorithms, encapsulate each one, and make them interchangeable. Strategy lets the algorithm vary without any change in clients that use it.
It is a common solution for representing a family of algorithms and letting you choose among them at runtime.
Strategy pattern consist of following components:
- An interface to represent some algorithm (the interface Strategy)
- One or more concrete implementations of that interface to represent multiple algorithms (the concrete classes ConcreteClassA, ConcreteClassB)
- One or more clients that use the strategy objects
Example of family of algorithms can be sorting algorithms (insert sort, quick sort, merge sort and so on), hashing algorithms (MD5, SHA), mathematical operation (addition, subtraction,multiplication),payment methods (credit card, Paypal, wallet). In strategy pattern, we try to define each member of the family and encapsulate implementation via an interface. Basic tenet of this pattern is: encapsulate anything which can change.
Lets talk about some problems and see how strategy pattern solves our maintenance nightmares.
Sorting algorithm using strategy pattern
First example is very simple, easy to understand and make the concept very clear. Example is about sorting algorithm. We want to implement a sorting algorithm, which works on given input and return output in sorted order. There are more sorting algorithms than I have underpants. Which one should we use? Let’s say we deliberate and come up with one sorting algorithm which satisfies our current requirements, let it be insert sort.
We will implement a function insert(List<Integer> input) and use it in clients which needs input to be sorted.
Going down, we want to change sorting algorithm to quick sort instead of insertion sort. And the hell will break lose. How many place you need to update the code because there will be thousands of clients who will be using it? There is a way out. We implement quick sort and a function fun() which calls insertion sort or quick sort based on input.
Well, now if we want to implement merge sort, our dear function fun() will have another if and else block. And if in future, a new algorithm is discovered, we need to add additional block for that. So, it is quite evident that code is not extensible and open for the change.
So, let’s get back to tenet of strategy pattern, which says, code which can be changed needs to be encapsulated. Best way to encapsulate things is interface. We will define an interface say ISortingAlgorithm, which declares a function called sort().
Now, we will define three different classes one each for insertion sort, merge sort, quick sort. Each of these classes implement ISortingAlgorithm.
Any client who wants to use, will send its choice of sorting algorithm. With this approach, any new sorting algorithm discovered can be easily be accommodated. Let’s see the code.
Hashing algorithm using strategy pattern
Second example is classic example which is used to explain strategy pattern and the example is of hashing. Let’s say, we are writing a hash function. At the time of design, we zeroed in on MD5 as our hashing function and we neatly defined and released function. Our function is so good that it is being used in so thousands of clients. Definition of our hash function is something like this:
Things go wrong when one fine day we find that MD5 hashing function has a flaw or MD5 hashing is not what we want. We deliberate and come up with another algorithm to be used say SHA256. And this leads to change to our hash function which we neatly written early. Now, our hashing function becomes:
Problem with above solution is that we still need to support MD5 implementation because, as I said, code has been already released and there are thousands of clients using that. We cannot afford to change all those clients, hence need to support the older API too. Well, we salvaged the situation. But thing to ponder is what if there comes another hashing algorithm which is more efficient than Md5 and SHA256 and we want to change our function and support all legacy implementation and usages. With the golden rule, that if code can change, encapsulate it, and what better way than interface to encapsulate things. We defined an interface let’s say IHasher with one function named hash().
Our earlier Hashing function now takes two parameters, one input to be hashed and other IHasher object. Implementation becomes:
We now create a class for each of the hashing algorithm and class implements IHasher interface.
And now, we call hash(“test String”, new MD5Hasher()) instead of hash(“test string”). However, if need to change MD5 to SHA256, we still need to replace all call with MD5Hasher() with SHA256Hasher(). Again maintenance nightmare. To avoid that we will create and pass objects as IHasher and not of actual class as shown in code below and there is no problem no matter how many times our algorithm changes. We don’t need to change our code. We add another class for that algorithm which implements IHasher interface and we are done. We that is principle is called as open for extension but not for modification. Our core logic remains same, still we can extend it to as many hashing algorithms we can thing of.
Payment Method using strategy pattern
Last example is related payment method. We have a shopping cart and then we have to associate a payment method to that cart. Payment method can be credit card or net banking or Cash on Delivery. To encapsulation of payment method, we will define an interface IPaymentMethod.
Now we implement class one each for payment method and then a test class to verify that correct payment method is used.
I think no need to explain this example,as it is quite self-explanatory.
As per Gang of Four Strategy patterns book, use strategy pattern when:
- Many related classes differ only in behaviour. Strategies provide a way to configure a class with one of many behaviour.
- You want different variants of same algorithm, like MD5 and SHA256 variants of hashing algorithm.
- Algorithm uses some data which should not be exposed to client. Pattern provides a way to hide complex and algorithm specific data to clients.
- A class define many behaviour and that appears as if-else block. To conclude, we today understood what is strategy design pattern and when to apply it. We also discussed some examples which are good candidates for application of strategy pattern.