© Vaskaran Sarcar 2019
Vaskaran SarcarJava Design Patternshttps://doi.org/10.1007/978-1-4842-4078-6_15

15. Strategy (Policy) Pattern

Vaskaran Sarcar1 
(1)
Bangalore, Karnataka, India
 

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

Before you proceed, let’s keep in mind the following points.
  • 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

Figure 15-1 shows the class diagram.
../images/395506_2_En_15_Chapter/395506_2_En_15_Fig1_HTML.jpg
Figure 15-1

Class diagram

Package Explorer View

Figure 15-2 shows the high-level structure of the program.
../images/395506_2_En_15_Chapter/395506_2_En_15_Fig2_HTML.jpg
Figure 15-2

Package Explorer view

Implementation

Here’s the implementation.

// Vehicle.java
package jdp2e.strategy.demo;
//Context class
public abstract class Vehicle
{
    /*A context object contains reference variable/s for the strategy object/s interface type.*/
    TransportMedium 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();
}
// Boat.java
package jdp2e.strategy.demo;
public class Boat extends Vehicle
{
    public Boat()
    {
        transportMedium= new WaterTransport();
    }
    @Override
    public void showMe() {
        System.out.println("I am a boat.");
    }
}
// Aeroplane.java
package jdp2e.strategy.demo;
public class Aeroplane extends Vehicle
{
    public Aeroplane()
    {
        transportMedium= new AirTransport();
    }
    @Override
    public void showMe() {
        System.out.println("I am an aeroplane.");
    }
}
// TransportMedium.java
package jdp2e.strategy.demo;
public interface TransportMedium
{
    public void transport();
}
//WaterTransport.java
package jdp2e.strategy.demo;
//This class represents an algorithm/behavior .
public class WaterTransport implements TransportMedium
{
    @Override
    public void transport()
    {
        System.out.println("I am transporting in water.");
    }
}
//AirTransport.java
package jdp2e.strategy.demo;
//This class represents an algorithm/behavior.
public class AirTransport implements TransportMedium
{
    @Override
    public void transport()
    {
        System.out.println("I am transporting in air.");
    }
}
// StrategyPatternExample.java
package jdp2e.strategy.demo;
//Client code
public 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();
    }
}

Output

Here’s the output.
***Strategy Pattern Demo***
I am a boat.
I am transporting in water.
________
I am an aeroplane.
I am transporting in air.

Q&A Session

  1. 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.

Let’s assume that your vehicle class has the following construct.
abstract class Vehicle
{
    //Default implementation
    public void showTransportMedium()
    {
        System.out.println("I am transporting in air.");
    }
    //The code that does not vary.
    public void commonJob()
    {
        System.out.println("We all can be used to transport");
    }
    public abstract void showMe();
}
So, make a concrete implementation of Vehicle, like this:
class Aeroplane extends Vehicle
{
    @Override
    public void showMe() {
        System.out.println("I am an aeroplane.");
    }
}
And use following lines of codes in client class.
Aeroplane aeroplane=new Aeroplane();
aeroplane.showMe();
aeroplane.showTransportMedium();
You will receive following output:
I am an aeroplane.
I am transporting in air.
So far, it looks good. Now suppose that you have introduced another class, Boat, like in the following.
class Boat extends Vehicle
{
    @Override
    public void showMe() {
        System.out.println("I am a boat.");
    }
}
Use the following lines of codes in the client class (new lines are shown in bold).
Aeroplane aeroplane=new Aeroplane();
aeroplane.showMe();
aeroplane.showTransportMedium();
Boat boat=new Boat();
boat.showMe();
boat.showTransportMedium();
You receive the following output.
I am an aeroplane.
I am transporting in air.
I am a boat.
I am transporting in air.

You can see that your boat is moving into the air now. To prevent this ugly situation, you need to override it properly.

Now further assume that you need to introduce another class, SpeedBoat, which can also transport through water at high speed. You need to guard the situations like this:
class Boat extends Vehicle
{
    @Override
    public void showMe()
    {
        System.out.println("I am a boat.");
    }
    @Override
    public void showTransportMedium() {
        System.out.println("I am transporting in water.");
    }
}
class SpeedBoat extends Vehicle
{
    @Override
    public void showMe() {
        System.out.println("I am a speedboat.");
    }
    @Override
    public void showTransportMedium() {
        System.out.println("I am transporting in water with high speed.");
    }
}
You can see that if you spread out the task that can vary across different classes (and their subclasses), in the long run, maintenance becomes very costly. You can experience a lot of pain if you want to accommodate similar changes very often, because you need to keep updating the showTransportMedium() method in each case.
  1. 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
    {
        @Override
        public void showMe() {
            System.out.println("I am an aeroplane.");
        }
        @Override
        public void showTransportMedium() {
            System.out.println("I am transporting in air.");
        }
    }
    class Boat extends Vehicle implements TransportInterface
    {
        @Override
        public void showMe()
        {
            System.out.println("I am a boat.");
        }
        @Override
        public 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.

     
  2. 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 AirTransport
            transportMedium= new AirTransport();
        }
        @Override
        public 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 class
    public abstract class Vehicle
    {
        //A context object contains reference variable/s
        //for the strategy object/s interface type
        TransportMedium 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 code
    public 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 vehicle
            vehicleContext.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.

     
  3. 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.

     
  4. 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.)

     
  5. 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.

     
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.133.107.25