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:
- Strategy Interface: Declares the operations that all concrete strategies must implement.
- Concrete Strategies: Implement the algorithm using the Strategy interface.
- 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
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
- Implement a new sorting strategy using the Merge Sort algorithm.
- 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.
Software Design Patterns Course
Module 1: Introduction to Design Patterns
- What are Design Patterns?
- History and Origin of Design Patterns
- Classification of Design Patterns
- Advantages and Disadvantages of Using Design Patterns
Module 2: Creational Patterns
Module 3: Structural Patterns
Module 4: Behavioral Patterns
- Introduction to Behavioral Patterns
- Chain of Responsibility
- Command
- Interpreter
- Iterator
- Mediator
- Memento
- Observer
- State
- Strategy
- Template Method
- Visitor
Module 5: Application of Design Patterns
- How to Select the Right Pattern
- Practical Examples of Pattern Usage
- Design Patterns in Real Projects
- Refactoring Using Design Patterns
Module 6: Advanced Design Patterns
- Design Patterns in Modern Architectures
- Design Patterns in Microservices
- Design Patterns in Distributed Systems
- Design Patterns in Agile Development