Engineering Full Stack Apps with Java and JavaScript
The strategy pattern is a behavioral pattern that defines a family of algorithms, encapsulates each one of them and makes them interchangeable. According to this pattern, a different class is created for each interchangeable algorithm, and this class can be injected anytime, even at runtime.
Important components of strategy pattern are:
Strategy interface, which declares the abstract strategy method.
Concrete Strategy Class, that provides implementation for the Strategy
Context Class, that has a reference to the Strategy interface, will be provided with a concrete implementation of the Strategy at runtime and will delegate the strategy behavior call to the provided implementation.
The base class can be called the context class. All the related behavior classes will have a common interface (or abstract class), which has a method declared for this algorithm. This is the Strategy interface. There will be a reference type of this abstract parent as a member variable in the context class and we can inject the right behavior class implementation (Concrete Strategy) based on the algorithm required, even at runtime. The context class receive requests and delegates it to the injected strategy object. Strategy design pattern is also called Policy pattern.
Steps to use strategy pattern can be summarized as:
Identify an algorithm that may vary.
Separate it into an interface with a method declared for the behavior.
Provide implementations for the separated interface.
Code within the context class against the interface and inject the implementation at runtime
Strategy lets the algorithm vary independent of the classes that uses them.
Strategy pattern is used when we need different variants of an algorithm within our class.
Example 1: array class with a sort method
We can have an array class with a sort method, and the sort technique to use may vary from bubble sort, merge sort, quick sort, insertion sort etc. based on various criteria. In this case, we can have an interface Sortable with a Sort method and its implementations as bubble sort, merge sort, quick sort, insertion sort etc. In the array class, we can have a sort method and then inject the implementation based on the criteria at runtime. Whenever someone call a sort of the array class, we can delegate the call to Sortable.sort(), which will call the sort() method of the implementation that is injected in.
public interface Sortable {
public void sort();
}
public class MergeSort implements Sortable{
public void sort()
{
//Implementation for merge sort
}
}
MyArray{
Sortable sortable;
…
MyArray()
{
sortable = new BubbleSort(); //default
}
…
setSortable(Sortable sortable)
{
this.sortable = sortable;
}
Sort()
{
sortable.sort();
}
}
Client code will pass on an implementation to the context class as:
MyArray myarray = new MyArray();
myarray.setSortable(new MergeSort());
myarray.sort();
You can now change the behavior even at runtime.
Example 2: Duck simulator class
The book Head First design patterns, give the example of a Duck simulator class. Duck may have behavior like fly, quack, swim etc. While some of these methods like swim are common for all, certain behavior like fly and quack may be different for different ducks in a simulation environment. One possibility is to have a common behavior in the parent and let all the children ducks override it. However if there are behavior which are common to more ducks, then all that ducks will need to duplicate that code everywhere that behavior is required. This will lead to the violation of the principle ‘Don’t repeat yourself (DRY)’.
There is also a probability of forgetting to override the common parent behavior which may lead to invalid cases. For instance, if the default behavior is fly and you forget to override it with no fly in a rubber duck, your rubber duck will be able to fly, which is an invalid case. To solve this, you may be tempted to use an interface and then implement the required behavior in each Duck class. However if there are behavior which are common to more ducks, then all that ducks will need to duplicate that code everywhere that behavior is required. This will again lead to the violation of the principle ‘Don’t repeat yourself (DRY)’.
So a possible solution here is to use the strategy pattern. Encapsulate the fly behavior to a Flyable interface. The Flyable interface can have subclasses like FlyWithWings, NoFly etc. Duck class can have a reference to Flyable interface and we can inject FlyWithWings to ducks that can fly and NoFly to ducks like rubber ducks that can’t fly.
We are encapsulating the algorithm that varies and separating it into another class, which is what the principle ‘Encapsulate what varies’ suggest.
We are also having a reference type of the abstract parent as a member variable in the class and injecting the right behavior class implementation, which is in accordance with the principle ‘Program to interfaces’.
Instead of having this behavior in parent class and making all classes to override it, we are having the behavior class as a member of the context class, which is now a HAS-A relationship instead of IS-A relationship, thus preferring composition over inheritance.
The context class received requests and delegates it to the injected strategy object. Hence it can also make use of the Dependency inversion principle (DIP).
Strategy pattern also avoids the violation of the principle ‘Don’t repeat yourself (DRY)’ (refer to the second example for more details).
From an interview perspective, it would be nice to know the differences between related patterns:
Strategy vs. Bridge
Though the diagrams for both Strategy and Bridge is same, they are different patterns. Different questions may have same answer, but the questions will still be different. Bridge is a structural pattern, whereas Strategy is a behavioral pattern.
The intent of Bridge pattern is to reduce the complexity in inheritance through the use of HAS-A along with IS-A, whereas the intent of Strategy pattern is to replace behavior (algorithms), and again through the use of HAS-A along with IS-A.
Strategy vs. State
Both Strategy and State are behavioral patterns.
In case of strategy it is the client that decide which replacement part (algorithm) to use, by injecting the right implementation class. However, in case of state, it is the context object that decides which replacement part (state object) to use, based on the current state.
Strategy vs. Template
Both Strategy and Template are behavioral patterns.
Template pattern provide an abstract parent template class and uses inheritance to fill in the template class by its Children. A strategy pattern's replacement behavior is encapsulated into external classes and uses composition to use the right behavior.