Strategy Design Pattern in Java

Strategy Design Pattern in Java | In project development, we develop multiple classes having a dependency. The class that takes the service/support of other classes is called the target class and the class that acts as helper class is called the dependent class. Strategy design pattern allows to development of usable, flexible, extensible, easily maintainable software applications as a loosely coupled collection of interchangeable parts. 

Strategy design pattern says “Develop the classes of dependency management (target, dependent classes) as the loosely coupled interchangeable parts.” So that we can replace one dependent with another dependent without disturbing the source code of the target class.

Note:- The strategy design pattern is not specific to the Spring framework but it becomes popular because of Spring. It is a GOF design pattern and can be used in any OOP language.

Some examples of target and dependent classes,

Target ClassDependent Class
VehicleDieselEngine / PetrolEngine / CNGEngine and e.t.c.
StudentJava Course / Python Course / PHP Course
DAO ClassApache DBCP / HikariCP / TomcatCP
Service ClassOracleEmployee DAO / MySQLEmployee DAO (Persistance logic)

It is all about developing classes of dependency management-based bunch rules to make them loosely coupled interchangeable parts. Taking classes having dependency is called dependency management where the target class should be in a position to change related dependents having loose coupling. The strategy pattern is used to create an interchangeable family of algorithms from which the required process is chosen at run-time.

Strategy Design Pattern Rules

Developing the classes of dependency management (target and dependent classes) based strategy design pattern is all about implementing rules/principles.

Following are the 3 Principles of Strategy Design Pattern:-

  1. Prefer composition over inheritance.
  2. Always code to interfaces/abstract class, never code to implementation classes/concrete classes to achieve loose coupling.
  3. Our code must be open for extension and must be closed for modification.

1. Prefer Composition over Inheritance

The first principle says:- “Prefer composition over inheritance.

Example of inheritance (IS-A relation)

class A { }
class B extends A { }

Example of composition (HAS-A relation)

class A { }
class B {
   A a = new A(); // HAS-A
}

In problems/limitations with inheritance in Java, we have already discussed several problems related to inheritance in Java and their solutions. And in the decorator/wrapper design pattern we had discussed how always using inheritance for adding functionalities in the existing object is not a good idea. 

In the book Effective Java, author Joshua Bloch writes that programmers should “Design and document for inheritance or else prohibit it“.

Note:- Based on the above points don’t conclude that inheritance is bad and we should not use it. When we should use inheritance and when we should use composition? If class B wants to use all the properties and methods of class A (i.e. total functionality of class A) then we should go for inheritance. But if class B wants to use partial properties and partial methods of class A then we should go for composition (HAS-A relation). 

2. Always Code to Interfaces/Abstract class

The second principle says:- “Always code to interfaces/abstract class, never code to implementation classes/concrete classes to achieve loose coupling.

Problem:- 

Dependent classes,

public class DieselEngine { }
public class ElectricEngine { }

Target class,

public class Vehicle {
   private DieselEngine engine = new DieselEngine();
   // remaining logic
}

In the target class i.e. Vehicle class if we want to change dependent from the diesel engine to electric engine then we must modify the source code of the target class. That means it is providing tight coupling. 

Solution:- Make all possible dependent classes implemented from the common interface and take that interface reference variable in the target class as a HAS-A property. So, from outside of the target class we can assign our choice dependent class object.

Dependent classes,

public interface IEngine { }
public class DieselEngine implements IEngine { }
public class ElectricEngine implements IEngine { }

Target class,

public class Vehicle {
   // HAS-A property of type interface
   private IEngine engine; 
   public void setEngine(Engine engine) {
      this.engine = engine;
   }
   // business logic
}

In Client application

// create dependent class object
IEngine engg = new DieselEngine();
// create target class object
Vehicle vehicle = new Vehicle();
// assign or inject dependent class 
// object to target class object
vehicle.setEngine(engg);

Now, from the client application if we pass DieselEngine() class object then Vehicle will be created with diesel engine, and similarly, if we pass ElectricEngine() class object then Vehicle will be created with an electric engine. Here we can change one dependent object to another dependent object without modifying the source code of the target. This indicates target and dependent classes have loose coupling.

// to change dependent
IEngine engg1 = new ElectricEngine();
vehicle.setEngine(engg1);

In the future, if we want to add GasEngine() then just implement it from IEngine() interface and use it in the client application. No need to modify the code of any dependent class.

3. Code must be open for extension and closed for modification

The third principle says:- “Our code must be open for extension and must be closed for modification.

How code can be open for the extension? If we follow the second principle of strategy design pattern (i.e. working with interface model assignment) then our code automatically becomes open for the extension because we can add more implementation classes for common interface as a new dependent class to the target class. For example:- We already have an IEngine interface, after sometimes GasEngine and SolarEngine came into the market then we can simply implement them from the interface.

public class GasEngine implements IEngine { }
public class SolarEngine implements IEngine { }

How code can be closed for modification, i.e. no one should have access to the logic? Use final class or at least final methods. While developing the classes of application, if we take those classes as the final classes or its methods as final methods then application and its classes become closed for modification. We can’t develop subclasses for the final class and we can’t override the final methods of the superclass in sub-class.

String class, Wrapper classes, and e.t.c. classes are given as final classes for not allowing developers to develop sub-classes to override the logic. These are the most important classes in Java. Assume if String, StringBuilder classes will not be given as final classes then one developer can develop their own subclasses and override the string manipulation methods then it may create lots of problems for the other programmers working on the same project. Hence these classes are closed for modification.

// dependent components
public interface IEngine { }
public final class DieselEngine implements IEngine { }
public final class ElectricEngine implements IEngine { }
public final class GasEngine implements IEngine { }
public final class SolarEngine implements IEngine { }
// target component
public final class Vehicle { }

Note:- Though we are following a strategy design pattern, but sometimes we can’t take classes as final classes. Therefore taking classes as a final class or taking methods as final methods is not strict to implement in all permutations and combinations. Wherever possibility is there do it, else leave it.

If we are using a strategy design pattern in the environment where underlying servers/containers/frameworks are forced to generate InMemory subclasses for our application classes then taking our application classes as final classes is practically not possible. Some examples where we can’t declare our class as a final class or its methods as final methods:-

  • In spring programming, while working with LookupMethodInjection, MethodReplacer, Spring AOP, and e.t.c. concepts.
  • In Hibernate programming, while working with session.load(-,-) method with lazy=“true” and proxy= “<proxy-interface-name>”

Strategy Design Pattern Example

Project Structure,

StrategyDP
  => src
    => com.kp.comp
       => IEngine.java (I)
       => ElectricEngine.java
       => DieselEngine.java
       => Vehicle
    => com.kp.factory
       => VehicleFactory.java
    => com.kp.test
       => Test.java

The com.kp.comp package contains dependent and target classes. The IEngine is an interface and ElectricEngine and DieselEngine are the dependent classes implementing IEngine interface. 

IEngine.java (interface),

package com.kp.comp;

public interface IEngine {
   public void start();

   public void stop();
}

ElectricEngine.java (dependent class),

package com.kp.comp;

public final class ElectricEngine implements IEngine {
   
   public ElectricEngine() {
      System.out.println("ElectricEngine:: 0-param constructor");
   }

   @Override
   public void start() {
      System.out.println("ElectricEngine is started");
   }

   @Override
   public void stop() {
      System.out.println("ElectricEngine is stoped");
   }
}

The DieselEngine is also developed in a similar way. The dependent and target classes are final classes to close for modification.

Vehicle.java (target class),

package com.kp.comp;

public final class Vehicle {
   private IEngine enginee; // HAS-A property

   public Vehicle() {
      System.out.println("Vehicle:: 0-param constructor");
   }

   public void setEnginee(IEngine enginee) {
      System.out.println("Vehicle.setEnginee()");
      this.enginee = enginee;
   }
   
   public void drive(String srcPlace, String destPlace) {
      System.out.println("Vehicle.drive()");
      enginee.start();
      System.out.println("Vehicle: Driving started at " + srcPlace);
      System.out.println("Vehicle: Driving is going on");
      enginee.stop();
      System.out.println("Vehicle: Driving ended at " + destPlace);
   }
}

It is better to give a factory to create vehicle objects by providing abstraction on the vehicle class object creation process. The VehicleFactory is using Factory design pattern.

VehicleFactory.java

package com.kp.factory;

import com.kp.comp.DieselEngine;
import com.kp.comp.ElectricEngine;
import com.kp.comp.IEngine;
import com.kp.comp.Vehicle;

// Implementation of Factory Design Pattern
public class VehicleFactory {
   
   /*  static factory to create and return Vehicle object
    *  by creating passed engine type
    */
   public static Vehicle getVehicle(String engType) {
      IEngine engg = null;
      if(engType.equalsIgnoreCase("diesel")) {
         engg = new DieselEngine();
      } else if(engType.equalsIgnoreCase("electric")) {
         engg = new ElectricEngine();
      } else {
         throw new IllegalArgumentException("Invalid Engine type");
      }
      
      // create target class object
      Vehicle vehicle = new Vehicle();
      // assign dependent class object to target class object
      vehicle.setEnginee(engg);
      // return target class object
      return vehicle;
   }
}

Now, let us develop the user or test class.

Test.java

package com.kp.test;

import com.kp.comp.Vehicle;
import com.kp.factory.VehicleFactory;

public class Test {
   public static void main(String[] args) {
      // use factory
      Vehicle vehicle = VehicleFactory.getVehicle("electric");
      vehicle.drive("Place-A", "Place-B");
   }
}

Output:-

ElectricEngine:: 0-param constructor
Vehicle:: 0-param constructor
Vehicle.setEnginee()
Vehicle.drive()
ElectricEngine is started
Vehicle: Driving started at Place-A
Vehicle: Driving is going on
ElectricEngine is stoped
Vehicle: Driving ended at Place-B

See Strategy Design Pattern example code at GitHub:- StrategyDP

Pre-defined Examples Implementing Strategy Design Pattern

There are many predefined classes which are implementing strategy design patterns. 

  • The java.util.Comparator#compare() executed by among others Collection#sort(). The compare() method compare two collections/objects but based on the object we passed they will be compared. To compare two list, or two set different logics are required. The compare() method internally contains the logic to compare the things based on the passed argument.
  • In javax.servlet.http.HttpServlet the service() and all doXxx() methods take HttpServletRequest and HttpServletResponse and implementor has to process them (and not to get hold of them as instance variables). Service method needs request and response object and they are specific to the server. Since parameters are HttpServletRequest and HttpServletResponse therefore we can pass any server specific request, response object. 
  • javax.servlet.Filter#doFilter():- The doFilter() method contains three parameters request, response, and filter chaining. Based on the underlying servlet container the implementation class name will be change and objects will change. 

If you enjoyed this post, share it with your friends. Do you want to share more information about the topic discussed above or do you find anything incorrect? Let us know in the comments. Thank you!

Leave a Comment

Your email address will not be published.