This chapter covers the strategy pattern.
GoF Definition
Define a family of algorithms, encapsulate each one, and make them interchangeable. Strategy lets the algorithm vary independently from the clients that use it.
Concept
Suppose there is an application where you have multiple algorithms and each of these algorithms can perform a specific task. A client can dynamically pick any of these algorithms to serve its current need.
The strategy pattern suggests that you implement these algorithms in separate classes. When you encapsulate an algorithm in a separate class, you call it a strategy. An object that uses the strategy object is often referred to as a context object. These “algorithms” are also called behaviors in some applications.
Real-World Example
Generally at the end of a soccer match, if team A is leading 1–0 over team B, instead of attacking they become defensive to maintain the lead. On the other hand, team B goes for an all-out attack to score the equalizer.
Computer world Example
Suppose that you have a list of integers and you want to sort them. You do this by using various algorithms; for example, Bubble Sort, Merge Sort, Quick Sort, Insertion Sort, and so forth. So, you can have a sorting algorithm with many different variations. Now you can implement each of these variations (algorithms) in separate classes and pass the objects of these classes in client code to sort your integer list.
Note
You can consider the java.util.Comparator interface in this context. You can implement this interface and provide multiple implementations of comparators with different algorithms to do various comparisons using the compare() method. This comparison result can be further used in various sorting techniques. The Comparator interface plays the role of a strategy interface in this context.
Illustration
The strategy pattern encourages you to use object composition instead of subclassing. So, it suggests you do not override parent class behaviors in different subclasses. Instead, you put these behaviors in separate classes (called a strategy) that share a common interface.
The client class only decides which algorithm to use; the context class does not decide that.
A context object contains reference variables for the strategy objects’ interface type. So, you can obtain different behaviors by changing the strategy in the context.
In the following implementation, the Vehicle class is an abstract class that plays the role of a context. Boat and Aeroplane are two concrete implementations of the Vehicle class. You know that they are associated with different behaviors: one travels through water and the other one travels through air.
These behaviors are placed in two concrete classes: AirTransport and WaterTransport. These classes share a common interface, TransportMedium. So, these concrete classes are playing the role of the strategy classes where different behaviors are reflected through the transport() method implementations.
In the Vehicle class, there is a method called showTransportMedium() . Using this method, I am delegating the task to the corresponding behavior class. So, once you pick your strategy, the corresponding behavior can be invoked. Notice that in the Vehicle class, there is a method called commonJob(),which is not supposed to vary in the future, so its behavior is not treated as a volatile behavior.
Class Diagram
Package Explorer View
Implementation
Here’s the implementation.
Output
Q&A Session
- 1.
Why are you complicating the example by avoiding simple subclassing of these behaviors?
In object-oriented programming, you may prefer to use the concept of polymorphism so that your code can pick the intended object (among different object types) at runtime, leaving your code unchanged.
When you are familiar with design patterns, most often, you prefer composition over inheritance.
Strategy patterns help you combine composition with polymorphism. Let’s examine the reasons behind this.
It is assumed that you try to use the following guidelines in any application you write:Separate the code that varies a lot from the part of code that does not vary.
Try to maintain the varying parts as freestanding as possible (for easy maintenance).
Try to reuse them as much as possible.
Following these guidelines, I have used composition to extract and encapsulate the volatile/varying parts of the code, so that the whole task can be handled easily, and you can reuse them.
But when you use inheritance, your parent class can provide a default implementation, and then the derived class changes it (Java calls it overriding it). The next derived class can further modify the implementation, so you are basically spreading out the tasks over different levels, which may cause severe maintenance and extensibility issues in the future. Let’s examine such a case.
You can see that your boat is moving into the air now. To prevent this ugly situation, you need to override it properly.
- 2.
If this is the case, you could create a separate interface, TransportInterface, and place the showTransportMedium() method in that interface. Now any class that wants to get the method can implement that interface also. Is this understanding correct?
Yes, you can do that. But this is what the code looks like:abstract class Vehicle{//The code that does not vary.public void commonJob(){System.out.println("We all can be used to transport");}public abstract void showMe();}interface TransportInterface{void showTransportMedium();}class Aeroplane extends Vehicle implements TransportInterface{@Overridepublic void showMe() {System.out.println("I am an aeroplane.");}@Overridepublic void showTransportMedium() {System.out.println("I am transporting in air.");}}class Boat extends Vehicle implements TransportInterface{@Overridepublic void showMe(){System.out.println("I am a boat.");}@Overridepublic void showTransportMedium() {System.out.println("I am transporting in water.");}}You can see that each class and its subclasses may need to provide its own implementations for the showTransportMedium() method. So, you cannot reuse your code, which is as bad as inheritance in this case.
- 3.
Can you modify the default behavior at runtime in your implementation?
Yes, you can. Let’s introduce a special vehicle that can transport in both water and air, as follows.public class SpecialVehicle extends Vehicle{public SpecialVehicle(){//Initialized with AirTransporttransportMedium= new AirTransport();}@Overridepublic void showMe(){System.out.println("I am a special vehicle who can transport both in air and water.");}}And add a setter method in the Vehicle class(changes are shown in bold).//Context classpublic abstract class Vehicle{//A context object contains reference variable/s//for the strategy object/s interface typeTransportMedium transportMedium;public Vehicle(){}public void showTransportMedium(){//Delegate the task to the corresponding behavior class.transportMedium.transport();}//The code that does not vary.public void commonJob(){System.out.println("We all can be used to transport");}public abstract void showMe();//Additional code to explain the answer of question no 3 in//the "Q&A session"public void setTransportMedium(TransportMedium transportMedium){this.transportMedium=transportMedium;}}To test this, add a few lines of code in the client class, as well.//Client codepublic class StrategyPatternExample {public static void main(String[] args) {System.out.println("***Strategy Pattern Demo***");Vehicle vehicleContext=new Boat();vehicleContext.showMe();vehicleContext.showTransportMedium();System.out.println("________");vehicleContext=new Aeroplane();vehicleContext.showMe();vehicleContext.showTransportMedium();System.out.println("________");//Additional code to explain the answer of question no//3 in the "Q&A session"vehicleContext=new SpecialVehicle();vehicleContext.showMe();vehicleContext.showTransportMedium();System.out.println("- - - - -");//Changing the behavior of Special vehiclevehicleContext.setTransportMedium(new WaterTransport());vehicleContext.showTransportMedium();}}Now if you execute this modified program, you get the following output.***Strategy Pattern Demo******Strategy Pattern Demo***I am a boat.I am transporting in water.________I am an aeroplane.I am transporting in air.________I am a special vehicle who can transport both in air and water.I am transporting in air.- - - - -I am transporting in water.The initial behavior is modified dynamically in a later phase.
- 4.
Can you use an abstract class instead of an interface?
Yes. It is suitable in some cases where you may want to put common behaviors in the abstract class. I discussed it in detail in the “Q&A Session” section on the builder pattern.
- 5.What are the key advantages of using a strategy design pattern?
This pattern makes your classes independent from algorithms. Here, a class delegates the algorithms to the strategy object (that encapsulates the algorithm) dynamically at runtime. So, you can simply say that the choice of the algorithm is not bound at compile time.
Easier maintenance of your codebase.
It is easily extendable. (Refer to the answers for questions 2 and 3 in this context.)
- 6.What are key challenges associated with a strategy design pattern?
The addition of context classes causes more objects in our application.
Users of the application must be aware of different strategies; otherwise, the output may surprise them. So, there exists a tight coupling between the client code and the implementation of different strategies.
When you introduce a new behavior/algorithm, you may need to change the client code also.