Strategy Pattern

How it works

Strategy Pattern
How it works
  1. Input arrives
  2. Core component processes
  3. Result produced
  4. Observe and iterate

Overview

Define a family of algorithms, encapsulate each one, and make them interchangeable.

Learning Objectives

🎯 Algorithm Encapsulation

Master the technique of encapsulating algorithms and making them interchangeable at runtime

🔧 Behavioral Flexibility

Learn to eliminate conditional logic by using polymorphism and strategy selection

⚡ Open/Closed Principle

Understand how strategies enable adding new algorithms without modifying existing code

Real-World Examples

💰 Pricing Strategies

Amazon's pricing engine uses different strategies (regular, prime, bulk discount, seasonal) to calculate product prices dynamically

Benefit: 45% increase in pricing flexibility

🚚 Route Optimization

Uber's routing system switches between strategies (fastest, shortest, eco-friendly) based on user preferences and traffic conditions

Benefit: 30% improvement in route satisfaction

🔍 Search Algorithms

Google Search employs different ranking strategies (relevance, recency, authority) that can be combined and weighted dynamically

Benefit: 85% better search result customization

🎮 Game AI Behavior

Video game NPCs use strategy patterns for different behavior modes (aggressive, defensive, stealth) that change based on game state

Benefit: 70% more engaging AI interactions

Code example

Switch sorting strategies at runtime without changing the client code:
interface SortStrategy<T> { sort(arr: T[]): T[] }

class QuickSortStrategy<T> implements SortStrategy<T> {
  sort(arr: T[]): T[] { return arr.slice().sort() }
}

class ReverseSortStrategy<T> implements SortStrategy<T> {
  sort(arr: T[]): T[] { return arr.slice().sort().reverse() }
}

class Sorter<T> {
  constructor(private strategy: SortStrategy<T>) {}
  setStrategy(s: SortStrategy<T>) { this.strategy = s }
  run(arr: T[]): T[] { return this.strategy.sort(arr) }
}

var sorter = new Sorter(new QuickSortStrategy());
var a = sorter.run([3, 1, 2]); // [1,2,3]
sorter.setStrategy(new ReverseSortStrategy());
var b = sorter.run([3, 1, 2]); // [3,2,1]

Implementation Notes

  • Define a common interface that all strategy implementations must follow
  • Use dependency injection or factory patterns to provide strategy instances
  • Consider using enum or configuration to map strategy names to implementations
  • Implement null object pattern for default strategy behavior
  • Use generic types when strategies work with different data types

Best Practices

  • Keep strategies stateless and focused on single algorithmic concerns
  • Use descriptive names that clearly indicate the strategy's behavior
  • Provide clear documentation about when to use each strategy
  • Consider strategy composition for complex scenarios requiring multiple algorithms
  • Implement proper error handling and validation within each strategy

Common Pitfalls

  • Creating too many strategies for minor algorithmic variations instead of parameterization
  • Coupling strategies to specific data structures instead of using abstractions
  • Forgetting to handle edge cases consistently across all strategy implementations
  • Making strategies stateful which can lead to threading and reusability issues
  • Not providing clear guidance on strategy selection criteria for users