Introduction

The Strategy pattern is a behavioral design pattern that enables selecting an algorithm's behavior at runtime. Instead of implementing a single algorithm directly, code receives run-time instructions as to which in a family of algorithms to use.

Key Concepts

  • Strategy Interface: Defines a common interface for all supported algorithms.
  • Concrete Strategies: Implement the algorithm defined by the strategy interface.
  • Context: Maintains a reference to a strategy object and delegates the algorithm execution to the strategy object.

When to Use the Strategy Pattern

  • When you have multiple algorithms for a specific task and want to switch between them dynamically.
  • When you want to avoid using conditional statements to select different behaviors.
  • When you want to isolate the code that varies from the code that stays the same.

Structure

The Strategy pattern involves three main components:

  1. Strategy Interface: Declares the operations that all concrete strategies must implement.
  2. Concrete Strategies: Implement the algorithm using the Strategy interface.
  3. Context: Maintains a reference to a Strategy object and delegates the algorithm execution to the Strategy object.

UML Diagram

+-------------------+       +-------------------+
|      Context      |       |     Strategy      |
|-------------------|       |-------------------|
| - strategy: Strategy |<----| + algorithm(): void |
|-------------------|       +-------------------+
| + setStrategy(s: Strategy): void |
| + executeAlgorithm(): void |
+-------------------+       +-------------------+
        |                               ^
        |                               |
        v                               |
+-------------------+       +-------------------+
| ConcreteStrategyA |       | ConcreteStrategyB |
|-------------------|       |-------------------|
| + algorithm(): void |       | + algorithm(): void |
+-------------------+       +-------------------+

Example

Let's consider a simple example where we have different strategies for sorting a list of numbers.

Strategy Interface

public interface SortStrategy {
    void sort(int[] numbers);
}

Concrete Strategies

public class BubbleSortStrategy implements SortStrategy {
    @Override
    public void sort(int[] numbers) {
        // Implementation of bubble sort algorithm
        int n = numbers.length;
        for (int i = 0; i < n-1; i++) {
            for (int j = 0; j < n-i-1; j++) {
                if (numbers[j] > numbers[j+1]) {
                    // Swap numbers[j] and numbers[j+1]
                    int temp = numbers[j];
                    numbers[j] = numbers[j+1];
                    numbers[j+1] = temp;
                }
            }
        }
    }
}

public class QuickSortStrategy implements SortStrategy {
    @Override
    public void sort(int[] numbers) {
        // Implementation of quick sort algorithm
        quickSort(numbers, 0, numbers.length - 1);
    }

    private void quickSort(int[] arr, int low, int high) {
        if (low < high) {
            int pi = partition(arr, low, high);
            quickSort(arr, low, pi - 1);
            quickSort(arr, pi + 1, high);
        }
    }

    private int partition(int[] arr, int low, int high) {
        int pivot = arr[high];
        int i = (low - 1);
        for (int j = low; j < high; j++) {
            if (arr[j] < pivot) {
                i++;
                int temp = arr[i];
                arr[i] = arr[j];
                arr[j] = temp;
            }
        }
        int temp = arr[i + 1];
        arr[i + 1] = arr[high];
        arr[high] = temp;
        return i + 1;
    }
}

Context

public class SortContext {
    private SortStrategy strategy;

    public void setStrategy(SortStrategy strategy) {
        this.strategy = strategy;
    }

    public void sort(int[] numbers) {
        strategy.sort(numbers);
    }
}

Client Code

public class StrategyPatternDemo {
    public static void main(String[] args) {
        SortContext context = new SortContext();

        int[] numbers = {5, 2, 9, 1, 5, 6};

        // Using Bubble Sort
        context.setStrategy(new BubbleSortStrategy());
        context.sort(numbers);
        System.out.println("Bubble Sorted: " + Arrays.toString(numbers));

        // Using Quick Sort
        context.setStrategy(new QuickSortStrategy());
        context.sort(numbers);
        System.out.println("Quick Sorted: " + Arrays.toString(numbers));
    }
}

Practical Exercise

Exercise

  1. Implement a new sorting strategy using the Merge Sort algorithm.
  2. Modify the client code to use the new Merge Sort strategy and test it with an array of numbers.

Solution

MergeSortStrategy

public class MergeSortStrategy implements SortStrategy {
    @Override
    public void sort(int[] numbers) {
        mergeSort(numbers, 0, numbers.length - 1);
    }

    private void mergeSort(int[] arr, int left, int right) {
        if (left < right) {
            int mid = (left + right) / 2;
            mergeSort(arr, left, mid);
            mergeSort(arr, mid + 1, right);
            merge(arr, left, mid, right);
        }
    }

    private void merge(int[] arr, int left, int mid, int right) {
        int n1 = mid - left + 1;
        int n2 = right - mid;

        int[] L = new int[n1];
        int[] R = new int[n2];

        for (int i = 0; i < n1; ++i)
            L[i] = arr[left + i];
        for (int j = 0; j < n2; ++j)
            R[j] = arr[mid + 1 + j];

        int i = 0, j = 0;
        int k = left;
        while (i < n1 && j < n2) {
            if (L[i] <= R[j]) {
                arr[k] = L[i];
                i++;
            } else {
                arr[k] = R[j];
                j++;
            }
            k++;
        }

        while (i < n1) {
            arr[k] = L[i];
            i++;
            k++;
        }

        while (j < n2) {
            arr[k] = R[j];
            j++;
            k++;
        }
    }
}

Modified Client Code

public class StrategyPatternDemo {
    public static void main(String[] args) {
        SortContext context = new SortContext();

        int[] numbers = {5, 2, 9, 1, 5, 6};

        // Using Merge Sort
        context.setStrategy(new MergeSortStrategy());
        context.sort(numbers);
        System.out.println("Merge Sorted: " + Arrays.toString(numbers));
    }
}

Common Mistakes and Tips

  • Not Defining a Clear Interface: Ensure that the strategy interface is well-defined and consistent across all concrete strategies.
  • Context Dependency: Avoid making the context dependent on specific strategies. The context should be able to work with any strategy that implements the interface.
  • Performance Considerations: Be mindful of the performance implications of switching strategies at runtime, especially in performance-critical applications.

Summary

The Strategy pattern is a powerful tool for designing flexible and maintainable code. By encapsulating algorithms within separate classes and making them interchangeable, you can easily extend and modify behavior without altering the context. This pattern is particularly useful when you have multiple ways to perform a task and want to switch between them dynamically.

© Copyright 2024. All rights reserved