Strategy Pattern
The Strategy is a behavioral design pattern that enables selecting an algorithm at runtime by encapsulating each one as a separate interchangeable class. It lets you define a family of algorithms, make them interchangeable, and vary them independently from the client that uses them.
Key Principles
- Encapsulate Algorithms: Each strategy is a class.
- Interchangeability: Client uses common interface; swaps strategies easily.
- Favor Composition: Context "has a" strategy (delegation), not inheritance.
- Open/Closed: Add new strategies without modifying context.
Structure
- Strategy Interface: Declares method for algorithm.
- Concrete Strategy: Implements specific algorithm.
- Context: Holds reference to strategy; delegates calls.
Python Example: Sorting Strategies
from abc import ABC, abstractmethod
from typing import List
# Strategy Interface
class SortStrategy(ABC):
@abstractmethod
def sort(self, data: List[int]) -> List[int]:
pass
# Concrete Strategies
class BubbleSort(SortStrategy):
def sort(self, data: List[int]) -> List[int]:
n = len(data)
for i in range(n):
for j in range(0, n-i-1):
if data[j] > data[j+1]:
data[j], data[j+1] = data[j+1], data[j]
return data
class QuickSort(SortStrategy):
def sort(self, data: List[int]) -> List[int]:
if len(data) <= 1:
return data
pivot = data[len(data)//2]
left = [x for x in data if x < pivot]
middle = [x for x in data if x == pivot]
right = [x for x in data if x > pivot]
return self.sort(left) + middle + self.sort(right)
class MergeSort(SortStrategy):
def sort(self, data: List[int]) -> List[int]:
if len(data) <= 1:
return data
mid = len(data) // 2
left = self.sort(data[:mid])
right = self.sort(data[mid:])
return self._merge(left, right)
def _merge(self, left, right):
result = []
i = j = 0
while i < len(left) and j < len(right):
if left[i] < right[j]:
result.append(left[i])
i += 1
else:
result.append(right[j])
j += 1
result.extend(left[i:])
result.extend(right[j:])
return result
# Context
class Sorter:
def __init__(self, strategy: SortStrategy):
self.strategy = strategy
def set_strategy(self, strategy: SortStrategy):
self.strategy = strategy
def sort_data(self, data: List[int]) -> List[int]:
print(f"Using {self.strategy.__class__.__name__}")
return self.strategy.sort(data.copy()) # Return sorted copy
# Usage (Client Code)
if __name__ == "__main__":
data = [64, 34, 25, 12, 22, 11, 90]
sorter = Sorter(BubbleSort())
print(sorter.sort_data(data)) # Bubble sort
sorter.set_strategy(QuickSort())
print(sorter.sort_data(data)) # Quick sort
sorter.set_strategy(MergeSort())
print(sorter.sort_data(data)) # Merge sort
Output:
Using BubbleSort
[11, 12, 22, 25, 34, 64, 90]
Using QuickSort
[11, 12, 22, 25, 34, 64, 90]
Using MergeSort
[11, 12, 22, 25, 34, 64, 90]
When to Use Strategy
- Multiple Algorithms: Varying behavior (e.g., sorting, payment methods).
- Runtime Switching: Choose algorithm dynamically (e.g., based on data size).
- Avoid Conditional Logic: Replace if/else chains with strategy selection.
- Avoid: Single algorithm or tight coupling needed.
Summary: Strategy defines a family of interchangeable algorithms and lets the context use any one at runtime via composition, eliminating conditional sprawl.