Architecture & DesignImplementing Strategy Patterns in Java

Implementing Strategy Patterns in Java

The strategy design pattern is one of the common patterns found in the Java API library. This is very similar to another design pattern, called the State design pattern. This article describes the idea in brief with an example on how to implementation it in Java.

Overview

The strategy pattern is also called the policy pattern. It is categorized as a behavioral software design pattern where the emphasis is on finding a flexible communication pattern among objects. It helps in establishing flexible communication among runtime objects.

Strategy Pattern

The essential idea of the strategy pattern is to combine a set of operations in a small hierarchical extension of the class. The object connected to the strategy determines which algorithm is to be used in a given situation. For example, it enables us to exchange implementation details of an algorithm at run time without requiring us to rewrite it. This idea resonates with the pattern of implementation found in dependency injection because it also allows the implementation to be swapped out during testing, such as executing a mocked implementation in the test code.

It is similar to the state design pattern in the sense that, in the state design pattern, it is the object that encapsulates the state of a context object. An object in the strategy design pattern analogously encapsulates the implementation of an algorithm and can be swapped as per requirement, event at run time.

In the Java API library, the java.awt.Container components is an example of using this pattern. Here, the LayoutManager acts as the strategy object. The classes—such as BorderLayout, FlowLayout, and GridLayout—implement the interface LayoutManager. The implemented classes define a method named addLayoutComponent(). The definition inside this method determines the logic, or how the components would be laid out in the Container object. For example, a FlowLayout lays them from left to right, whereas the BorderLayout lays them into sections named: CENTER, NORTH, EAST, SOUTH, WEST. The Container class holds the strategy object called the LayoutManager object. Because the interface holds a reference to objects of the class that implements the interface, the strategy object can refer to the implemented class at any time.

Use of the Strategy Pattern

It is basically a collection of classes inherited from a common base class. It has same basic structure and common functions, but the difference lies in the way it is used. It can be said that it functions as per the strategy applied. The strategy pattern empowers you by providing runtime flexibility. Unlike other similar patterns, like state and command, this flexibility can be leveraged by creating a surrogate class that controls the choice of a particular strategy at run time.

Implementing the Strategy Pattern

Here is a quick implementation of this pattern. The idea is very simple, yet it is effective in picking out the required implementation and exchanging them as needed. Nothing fancy, the interface and the hierarchical structure of the classes creates the rendition, a strategy to be used in the code down the line.

Here, it enables us to write code using the Sort interface, and the code we want to sort does not need to be concerned about the algorithm used for the sorting. By programming the interface and the class structure that cater to a specific implementation, we can use the interface according to our need and change the strategy as soon as we need another. The idea is extensible in the sense that, at a later point, we can add more implementation. Any code written against the interface will not change but will be able to use the new implementation.

package org.mano.example;
public interface Sort {
   int [] sort(int[] nos);
}
package org.mano.example;
public class BubbleSort implements Sort{
   @Override
   public int [] sort(int[] nos) {
      System.out.println("n--- BUBBLE SORT strategy
         in action---n");
      int n = nos.length;
      for (int i = 0; i < n-1; i++)
      for (int j = 0; j < n-i-1; j++)
      if (nos[j] > nos[j+1])
      {
         int tmp = nos[j];
         nos[j] = nos[j+1];
         nos[j+1] = tmp;
      }
      return nos;
   }
}
package org.mano.example;
public class SelectionSort implements Sort{
   @Override
   public int [] sort(int [] nos) {
      System.out.println("n--- SELECTION SORT strategy
         in action ---n");
      int n = nos.length;
      for (int i = 0; i < n-1; i++)
      {
         int mindex = i;
         for (int j = i+1; j < n; j++)
            if (nos[j] < nos[mindex])
               mindex = j;
         int temp = nos[mindex];
         nos[mindex] = nos[i];
         nos[i] = temp;
      }
      return nos;
   }
}
package org.mano.example;
public class ShellSort implements Sort {
   @Override
   public int [] sort(int[] nos) {
      System.out.println("n--- SHELL SORT strategy
         in action ---n");
      int n = nos.length;
      for (int part = n/2; part > 0; part /= 2)
      {
         for (int i = part; i < n; i += 1)
         {
            int temp = nos[i];
            int j;
            for (j = i; j >= part && nos[j - part] >
               temp; j -= part) nos[j] = nos[j - part];
            nos[j] = temp;
         }
      }
      return nos;
   }
}
package org.mano.example;
import java.util.Random;
public class SortingApp {
   private Sort sortStrategy;
   public SortingApp(Sort sort){
      this.sortStrategy = sort;
   }
   public int [] sort(int[] data){
      return sortStrategy.sort(data);
   }
   public void changeStrategy(Sort anotherStrategy){
      sortStrategy = anotherStrategy;
   }
   public void printArray(int arr[])
   {
      int n = arr.length;
      for (int i=0; i<n; ++i)
         System.out.print(arr[i]+" ");
      System.out.println();
   }
   public static int [] getDummyData(){
      Random r = new Random();
      int [] data = new int [10];
      for (int i=0;i<10;i++)
         data[i] = r.nextInt(100);
      return data;
   }
   public static void main(String[] args){
      SortingApp app = new SortingApp(new BubbleSort());
      app.printArray(app.sort(getDummyData()));
      app.changeStrategy(new SelectionSort());
      app.printArray(app.sort(getDummyData()));
      app.changeStrategy(new ShellSort());
      app.printArray(app.sort(getDummyData()));
   }
}

Output

--- BUBBLE SORT strategy in action---

5 15 22 38 41 45 56 72 72 97

--- SELECTION SORT strategy in action ---

42 47 52 55 60 76 79 82 86 96

--- SHELL SORT strategy in action ---

11 13 19 24 27 33 47 72 72 88

Conclusion

The Strategy Pattern enables us to defer decisions about the implementation strategy to use until run time. When we use the Spring Framework to use XML as a configuration file to construct objects and their dependencies, this is read at run time. The main advantage of this pattern is that it leverages dynamic changes between implementations without any need for recompilation.

Get the Free Newsletter!
Subscribe to Developer Insider for top news, trends & analysis
This email address is invalid.
Get the Free Newsletter!
Subscribe to Developer Insider for top news, trends & analysis
This email address is invalid.

Latest Posts

Related Stories