Strategy Design Pattern


Intent

Define a family of algorithms, encapsulate each one, and make them interchangeable. Strategy lets the algorithm vary independently from the clients that use it.

Also known as

Policy

Explanation

Strategy in the Real World 

To explain the strategy in the real world, let's take the example of a software developer. If language isn't an issue I might ask a developer to write a piece of code for me to create a user interface. One developer's chosen language is Java, so he'll develop the UI with Swing. Meanwhile, the other developer decides to use C#. I don't mind, I've left the details of how to write the UI to the developers, and both have applied their own strategy. At any stage, the developer could change their strategy, choosing to use a different language if they feel it's necessary. It's all about dynamically changing behaviors.

Technical examples

1. One of the best example of strategy pattern is Collections.sort() method that takes Comparator parameter. Based on the different implementations of Comparator interfaces, the Objects are getting sorted in different ways.

2. One more use of the Strategy pattern would be saving files in different formats, running various sorting algorithms, or file compression.

3. To perform mathematical operations like addition, subtraction, division and multiplication, here we need to decide, which operation to perform on runtime.

Structure


Participants

1. Strategy
  • declares an interface common to all supported algorithms. 
  • context uses this interface to call the algorithm defined by a ConcreteStrategy.

2. ConcreteStrategy
  •  implements the algorithm using the Strategy interface.
3. Context 
  •  is configured with a ConcreteStrategy object.
  •  maintains a reference to a Strategy object.
  •  may define an interface that lets Strategy access its data.

Collaborations

  • Strategy and Context interact to implement the chosen algorithm. A context may pass all data required by the algorithm to the strategy when the algorithm is called. Alternatively, the context can pass itself as an argument to Strategy operations. That lets the strategy call back on the context as required.
  • A context forwards requests from its clients to its strategy. Clients usually create and pass a ConcreteStrategy object to the context;thereafter, clients interact with the context exclusively. There is often a family of ConcreteStrategy classes for a client to choose from.
Lets see how does It work In Java with few examples.

1. Implementation (Mathematical operations as Algorithms)

Refer the structure diagram for this implementation and lets create the components as per structure diagram.

Step 1 : Create an interface like Strategy.java

public interface Strategy {
   public int doOperation(int num1, int num2);
}
Step 2 : Create concrete classes implementing the same interface.
OperationAdd.java
public class OperationAdd implements Strategy{
   @Override
   public int doOperation(int num1, int num2) {
      return num1 + num2;
   }
}
OperationSubstract.java
public class OperationSubstract implements Strategy{
   @Override
   public int doOperation(int num1, int num2) {
      return num1 - num2;
   }
}
OperationMultiply.java
public class OperationMultiply implements Strategy{
   @Override
   public int doOperation(int num1, int num2) {
      return num1 * num2;
   }
}
operationDevision.java
public class OperationDevision implements Strategy{
   @Override
   public int doOperation(int num1, int num2) {
      return (num1/num2);
   }
}
Step 3 :  Create Context Class. 
Context.java
public class Context {
   private Strategy strategy;

   public Context(Strategy strategy){
      this.strategy = strategy;
   }

   public int executeStrategy(int num1, int num2){
      return strategy.doOperation(num1, num2);
   }
}
Step 4 : Use the Context to see change in behaviour when it changes its Strategy.
StrategyPatternDemo.java
public class StrategyPatternDemo {
   public static void main(String[] args) {
      Context context = new Context(new OperationAdd());  
      System.out.println("10 + 5 = " + context.executeStrategy(10, 5));

      context = new Context(new OperationSubstract());  
      System.out.println("10 - 5 = " + context.executeStrategy(10, 5));

      context = new Context(new OperationMultiply());  
      System.out.println("10 * 5 = " + context.executeStrategy(10, 5));
   }
}
Step 5 : Verify the output.
10 + 5 = 15
10 - 5 = 5
10 * 5 = 50

2. Implementation (File Compression Tool )

Refer the structure diagram for this implementation and lets create the components as per structure diagram.
Let's use the example of a file compression tool - where we create either zip or rar files. First we'll need a strategy: 
//Strategy Interface
public interface CompressionStrategy {
  public void compressFiles(ArrayList<File> files);
}
And we'll need to provide our two implementations, one for zip and one for rar
public class ZipCompressionStrategy implements CompressionStrategy {
  public void compressFiles(ArrayList<File> files) {
    //using ZIP approach
  }
}

public class RarCompressionStrategy implements CompressionStrategy {
  public void compressFiles(ArrayList<File> files) {
    //using RAR approach
  }
}
Our context will provide a way for the client to compress the files. Let's say that there is a preferences setting in our application that sets which compression algorithm to use. We can change our strategy using the setCompressionStrategy method in the Context.

public class CompressionContext {
  private CompressionStrategy strategy;
  //this can be set at runtime by the application preferences
  public void setCompressionStrategy(CompressionStrategy strategy) {
    this.strategy = strategy;
  }
  
  //use the strategy
  public void createArchive(ArrayList<File> files) {
    strategy.compressFiles(files);
  }
}
It's obvious that all the client has to do now is pass through the files to the CompressionContext.

public class Client {
  public static void main(String[] args) {
    CompressionContext ctx = new CompressionContext();
    //we could assume context is already set by preferences
    ctx.setCompressionStrategy(new ZipCompressionStrategy());
    //get a list of files...
    ctx.createArchive(fileList);
  }
}

View source code on my github repository :

Applicability

Use the Strategy pattern when
  • many related classes differ only in their behavior. Strategies provide a way to configure a class either one of many behaviors.
  • you need different variants of an algorithm. for example, you might define algorithms reflecting different space/time trade-offs. Strategies can be used when these variants are implemented as a class hierarchy of algorithms.
  • an algorithm uses data that clients shouldn't know about. Use the Strategy pattern to avoid exposing complex, algorithm-specific data structures.
  • a class defines many behaviors, and these appear as multiple conditional statements in its operations. Instead of many conditionals, move related conditional branches into their own Strategy class.

Credits


  • Design Patterns: Elements of Reusable Object-Oriented Software

  • Comments