Saturday, February 29, 2020

Strategy Pattern

While writing code in OOP we make heavy use of features like inheritance. Inheritance has a major advantage of code re-usability, but it also has some design problems. 
When we create a child class inheriting from a parent class, we create a strong coupling. Any change in parent class also impacts child class too. Every time parent class changes, we need to be sure that child classes' behaviour has not changed unexpectedly, which in turn needs time and efforts spend on testing. If changes are not very frequent and big, we may live with it. But if that is not the case, it may be problematic.
Another problem with inheritance is when some child classes don't want certain behaviours inherited. Programmers try to overcome this problem by overriding the behaviour and providing an empty implementation. This is a hack, not a clean code. Again, this is affordable if we have to do it for rare cases. But if there are too many child classes needing this approach or too many behaviours like this, then this is not a very good way of doing things.
We may be tempted to make use of interfaces here. We declare interfaces for such behaviours and make child classes to implement them only if they need that behaviour. But this badly hurts code re-usability. We may end up with tons of duplicate code in implementations of interfaces. And if we have to make a change a behaviour common to all the implementations, we need to make change in all the implementations. And if its frequent changes, lots of problems to be faced.

Strategy design pattern helps to solve in such scenarios when some parts of our program change too frequently.

Strategy pattern approach is to separate away those parts of our program which vary frequently from those parts which don't, and allow them to evolve in isolation so that they don't impact other parts of the program.


Code Structure



Implementation wise we define interfaces for the behaviours and then implement these interfaces specifically. In other parts of program we make use of these implementations using HAS-A relationship(prefer using interfaces rather than concrete implementations). If  a behaviour has to be changed, those changes are limited to implementation classes and client code is not impacted. We can add new implementations too without changing client code. Whole idea is to ensure that frequently changing code is not part of client code which is stable and is separated out in different classes and used by plugging in those classes as and when needed. And we prefer to program to interfaces for plugging in.

Examples

Collections.sort() method takes a collection and a Comparator as inputs to sort the collection. Comparator interface has method 'compare' which compares two input arguments and returns the result. So Comparator is a 'strategy', and we can implement it as per our needs. Collections.sort is the client which is plugged with Comparator implementation to sort a collection. Concrete implementations of Comparator can evolve in isolation from rest of the program.

Another very good example of strategy pattern can be sorting algorithms. We can implement different sorting algorithms like bubble sort, merge sort, quick sort separately and client code can be plugged with concrete implementation as per need.