Low-level design2 min

Strategy Pattern

Type: Behavioral Pattern
Relevance: Extremely High. Arguably the most frequently used design pattern in modern OOP.

The Strategy pattern lets you define a family of algorithms, put each of them into a separate class, and make their objects interchangeable at runtime.

Real-Life Analogy

Think of Google Maps Routing. You want to get to the airport. Google Maps has a routing feature.

  • You can route via Car (Algorithm 1).
  • You can route via Walking (Algorithm 2).
  • You can route via Public Transit (Algorithm 3).

The overarching goal is the same (find a route), but the specific mathematical algorithm used to find the route changes drastically. You (the client) select the Strategy, and the Navigator executes it.

The Problem

If you have a class that does something specific in a lot of different ways, you end up with a massive class filled with if (strategy == "CAR") { ... } else if (strategy == "WALKING") { ... }. Adding a new algorithm (e.g., "Bicycle") requires modifying the core class, violating the Open/Closed Principle.

How to Implement Strategy

Extract all the algorithms into their own classes implementing a common Strategy interface. The original class (Context) maintains a reference to a Strategy object and delegates the work to it.

Example in Code

typescript
// 1. The Strategy Interface
interface RouteStrategy {
  buildRoute(A: string, B: string): void;
}

// 2. Concrete Strategies
class RoadStrategy implements RouteStrategy {
  buildRoute(A: string, B: string): void {
    console.log(`Calculating ROAD route from ${A} to ${B}... (Takes 30 mins)`);
  }
}

class WalkingStrategy implements RouteStrategy {
  buildRoute(A: string, B: string): void {
    console.log(`Calculating WALKING route from ${A} to ${B}... (Takes 3 hours)`);
  }
}

class PublicTransitStrategy implements RouteStrategy {
  buildRoute(A: string, B: string): void {
    console.log(`Calculating TRANSIT route from ${A} to ${B}... (Takes 45 mins)`);
  }
}

// 3. The Context
class Navigator {
  private strategy: RouteStrategy;

  constructor(strategy: RouteStrategy) {
    this.strategy = strategy;
  }

  // Allows swapping the strategy at runtime!
  public setStrategy(strategy: RouteStrategy): void {
    this.strategy = strategy;
  }

  public executeRouting(A: string, B: string): void {
    // The navigator delegates the complex math to the strategy
    this.strategy.buildRoute(A, B);
  }
}

// Client Code
const navigator = new Navigator(new RoadStrategy());
navigator.executeRouting("Home", "Airport"); 
// Output: Calculating ROAD route...

// User clicked the "Walking" icon! Swap the strategy at runtime.
navigator.setStrategy(new WalkingStrategy());
navigator.executeRouting("Home", "Airport");
// Output: Calculating WALKING route...

Class Diagram

Why is it Important?

  • Open/Closed Principle: You can add new algorithms without modifying the Context.
  • Composition over Inheritance: Replaces inheritance (creating CarNavigator, WalkingNavigator) with composition, making the system much more flexible.