Strategy Design Pattern explained in 2 minutes

Photo by JJ Ying on Unsplash

Strategy Design Pattern explained in 2 minutes

Practical guide to explain Strategy Design Pattern

Play this article

Problem Statement

If you want to dynamically make decisions in your class based on certain actions, then what’s the best way to achieve this?

public class OrderedList<T> {
    List<T> list;

    public OrderedList(List<T> list) {
        this.list = list;
    }

    public List<T> sort(String algo) {
        if ("bubble".equals(algo)) {
            return bubbleSort(list);
        } else if ("merge".equals(algo)) {
            return mergeSort(list);
        } else {
            throw new RuntimeException("Unsupported algo " + algo);
        }
    }
}

In the above code, if you need to change the sorting strategy based on the function argument, then the simplest way would be to use if/else and do a dispatch to the correct method body based on the argument.

Why would this be a problem?

Because, in the future, if you need to add a new strategy, you would have to update this class - which would violate the OCP (Open/Closed Principle) of the SOLID principle.

Solution?

public class OrderedList<T> {
    List<T> list;
    SortingStrategy<T> strategy;
    public OrderedList(List<T> list, SortingStrategy<T> strategy) {
        this.list = list;
        this.strategy = strategy;
    }

    // You can also update the code to take in Strategy at the time of sorting itself
    public List<T> sort() {
        return strategy.execute(list);
    }
}

public interface SortingStrategy<T> {
    List<T> execute(List<T> list);
}

public class MergeSort<T> implements SortingStrategy<T> {
    @Override
    public List<T> execute(List<T> list) {
        System.out.println("Performing merge sort");
        return list;
    }
}

public class BubbleSort<T> implements SortingStrategy<T> {
    @Override
    public List<T> execute(List<T> list) {
        System.out.println("Performing bubble sort");
        return list;
    }
}

public class Main {
    public static void main(String[] args) {
        testStrategy();
    }

    public static void testStrategy() {
        List<Integer> list = Arrays.asList(1, 2, 3, 4);
        BubbleSort<Integer> bubbleSort = new BubbleSort<>();
        MergeSort<Integer> mergeSort = new MergeSort<>();
        OrderedList<Integer> orderedList1 = new OrderedList<>(list, bubbleSort);
        orderedList1.sort();
        OrderedList<Integer> orderedList2 = new OrderedList<>(list, mergeSort);
        orderedList2.sort();
    }
}
// Output
Performing bubble sort
Performing merge sort

Using the strategy pattern, you can create new strategies and update the behavior of the class without modifying the class!

What's the benefit?

  • Follows the OCP principle - class should be open for extension but closed for modification. You can change the behavior of the class by injecting a new strategy at runtime.

  • Follows the SRP (Single Responsibility Principle) - class should have only one responsibility. Only one strategy is present in one class.

  • Follows the Composition over Inheritance principle suggested by GoF (Gang Of Four).

What's the drawback?

  • Creating new classes every time can create clutter if not done judiciously. Many languages allow you to create anonymous classes e.g. lambdas in Java - which can reduce the boilerplate code creation.

Did you find this article valuable?

Support Snehasish Roy by becoming a sponsor. Any amount is appreciated!