The strategy pattern is a behavioral pattern which lets you dynamically choose an algorithm from family of algorithms at runtime.

What is Strategy Pattern?

The strategy pattern is useful when you have multiple algorithms to choose from at runtime. You can avoid awkward looking if...else logic using strategy pattern. In this pattern a strategy class encapsulates an algorithm. The client also knows about what algorithm to choose and the client selects which strategy to execute. In standard Java library, java.util.Comparator is a good example of this pattern. The client decides how to compare two objects using this Comparator.

How to implement Strategy Pattern?

The strategy pattern mainly consists of following actors.

  • Context: This is the object on which the algorithm or strategy to be applied. It has a reference to one of the conrete strategies. The context object has a setter method which can be used to set the concrete strategy object.
  • Strategy: This is an interface which is implemented by concrete strategies. This interface also declares a method the context uses to execute a strategy. The concrete strategies implement the algorithm using the method declared in the interface. These concrete strategies also implement that method.
  • Client: The client knows about all strategies and actually creates a strategy object. It specifies which of the strategies to use for the context.

Practical Code - Order Discounts

In our example, we will write code for having multiple different order discount strategies.

  1. In the context class identify an algorithm, that’s changing based on if..else conditional. Based on this define a Strategy interface. In this case, I have defined DiscountStrategy interface which declares a method for getDiscount(price) with parameter price of the order.
1public interface DiscountStrategy {
2    double getDiscount(double price);
3}
  1. Based on this implement all possible discount strategies.

    a. ZeroDiscountStrategy applies zero discount on given order.

    1public class ZeroDiscountStrategy implements DiscountStrategy {
    2    @Override
    3    public double getDiscount(double price) {
    4        return 0;
    5    }
    6}
    

    b. FixedDiscountStrategy applies fixed amount of discount which is passed in the constructor parameter.

     1public class FixedDiscountStrategy implements DiscountStrategy {
     2    private final double discountAmount;
     3
     4    public FixedDiscountStrategy(double discountAmount) {
     5        this.discountAmount = discountAmount;
     6    }
     7
     8    @Override
     9    public double getDiscount(double price) {
    10        if (price < discountAmount) {
    11            return price;
    12        } else {
    13            return discountAmount;
    14        }
    15    }
    16}
    

    c. PercentageDiscountStrategy applies percentage of the cost of order.

     1public class PercentageDiscountStrategy implements DiscountStrategy {
     2    private final double discountPercentage;
     3
     4    public PercentageDiscountStrategy(double discountPercentage) {
     5        this.discountPercentage = discountPercentage;
     6    }
     7
     8    @Override
     9    public double getDiscount(double price) {
    10        return price * discountPercentage / 100;
    11    }
    12}
    
  2. The context class has to have a method to set the strategy. In this case, Order has a method to set discount strategy. Notice that it works with the interface.

 1public class Order {
 2    private double price;
 3    private DiscountStrategy discountStrategy;
 4
 5    public Order(double price, DiscountStrategy discountStrategy) {
 6        this.price = price;
 7        this.discountStrategy = discountStrategy;
 8    }
 9
10    public Order(double price) {
11        this(price, new ZeroDiscountStrategy());
12    }
13
14    public double getPrice() {
15        return price;
16    }
17
18    public void setPrice(double price) {
19        this.price = price;
20    }
21
22    public void setDiscountStrategy(DiscountStrategy discountStrategy) {
23        this.discountStrategy = discountStrategy;
24    }
25
26    public double getTotal() {
27        return price - discountStrategy.getDiscount(price);
28    }
29}
  1. Now, the client can required strategy as they wish.
 1public class Main {
 2    public static void main(String[] args) {
 3        Order order = new Order(100);
 4        System.out.println("Total price with zero discount: " + order.getTotal());
 5
 6        order.setDiscountStrategy(new FixedDiscountStrategy(10));
 7        System.out.println("Total price after 10$ discount: " + order.getTotal());
 8
 9        order.setDiscountStrategy(new PercentageDiscountStrategy(24));
10        System.out.println("Total price after 24% discount: " + order.getTotal());
11    }
12}

Advantages:

  • Each algorithm has its own class. This way code becomes more flexible and you can easily add new strategy without modifying existing ones.
  • Strategy pattern follows single responsibility principle making code maintainable.

Drawbacks:

  • It adds several additional classes to manage.
  • Client has to be aware of all available concrete strategies.

Summary

  • Strategy pattern is good pattern to have multiple interchangeable algorithms organized into their classes.
  • The client decides at runtime which strategy to use.
  • This can be useful to reduce conditional logic statements in the system. The client can simply decide the algorithm and set it on the context object.