Introduction to Strategy Pattern
What is the Strategy Pattern?
The Strategy Pattern is a behavioral design pattern that defines a family of algorithms, encapsulates each one in a separate class, and makes them interchangeable. This pattern lets the algorithm vary independently from clients that use it.
In essence, the Strategy pattern allows you to select an algorithm's behavior at runtime without using conditional statements.
The Problem
Imagine you're building a shipping rate calculator for a logistics platform. Different shipments require different pricing strategies:
- Weight-based: Charge based on package weight
- Dimensional: Charge based on package dimensions (dimensional weight)
- Flat-rate: Fixed price regardless of size/weight
- Zone-based: Price varies by origin and destination zones
Without the Strategy pattern, you might write code like this:
typescript1class ShippingCalculator {2 calculateRate(3 shipment: Shipment,4 method: string5 ): number {6 if (method === 'weight') {7 return shipment.weight * 0.5;8 } else if (method === 'dimensional') {9 const dimWeight = (shipment.length * shipment.width * shipment.height) / 166;10 return dimWeight * 0.6;11 } else if (method === 'flatRate') {12 return 15.99;13 } else if (method === 'zone') {14 const zoneMultiplier = this.getZoneMultiplier(15 shipment.origin,16 shipment.destination17 );18 return shipment.weight * zoneMultiplier;19 }20 throw new Error('Unknown method');21 }22}
Problems with this Approach
- Violates Open/Closed Principle: Adding new strategies requires modifying existing code
- Difficult to Test: All strategies are coupled in one method
- Hard to Maintain: Large conditional blocks become unreadable
- No Runtime Flexibility: Can't easily swap strategies dynamically
- Violates Single Responsibility: One class handles multiple algorithms
The Solution: Strategy Pattern
The Strategy pattern solves these issues by:
- Defining a Strategy Interface: All algorithms implement the same interface
- Creating Concrete Strategies: Each algorithm is encapsulated in its own class
- Context Class: Uses a strategy without knowing implementation details
- Runtime Selection: Strategies can be swapped at runtime
Pattern Structure
1┌─────────────────────────────────────────────────────────────┐2│ Client │3│ │4│ Selects and configures strategy, passes to context │5└─────────────────────────────────────────────────────────────┘6 │7 │ creates8 ▼9┌─────────────────────────────────────────────────────────────┐10│ Context │11│ ┌──────────────────────────────────────────────┐ │12│ │ - strategy: RateStrategy │ │13│ │ + setStrategy(strategy: RateStrategy): void │ │14│ │ + calculateRate(shipment: Shipment): number │ │15│ └──────────────────────────────────────────────┘ │16└─────────────────────────────────────────────────────────────┘17 │18 │ delegates to19 ▼20┌─────────────────────────────────────────────────────────────┐21│ <<interface>> │22│ RateStrategy │23│ ┌──────────────────────────────────────────────┐ │24│ │ + calculate(shipment: Shipment): number │ │25│ └──────────────────────────────────────────────┘ │26└─────────────────────────────────────────────────────────────┘27 △28 │ implements29 ┌───────────────┼───────────────┬──────────────┐30 │ │ │ │31┌───────────────────┐ ┌──────────────┐ ┌─────────────┐ ┌──────────────┐32│ WeightBased │ │ Dimensional │ │ FlatRate │ │ ZoneBased │33│ Strategy │ │ Strategy │ │ Strategy │ │ Strategy │34├───────────────────┤ ├──────────────┤ ├─────────────┤ ├──────────────┤35│ + calculate() │ │ + calculate()│ │+ calculate()│ │+ calculate() │36└───────────────────┘ └──────────────┘ └─────────────┘ └──────────────┘
Logistics Context: Shipping Rate Strategies
Let's see how different shipping strategies work in practice:
1. Weight-Based Strategy
Charges based on package weight:
- Formula:
weight × rate_per_pound - Use case: Standard parcels where weight is the primary factor
- Example: 5 lbs × $0.50/lb = $2.50
2. Dimensional Weight Strategy
Charges based on package size (volume):
- Formula:
(length × width × height) / divisor × rate - Use case: Large but lightweight packages (e.g., pillows, packaging materials)
- Example: (20 × 16 × 12) / 166 = 23.13 lbs dimensional weight
- Note: Carrier typically uses the greater of actual weight vs dimensional weight
3. Flat-Rate Strategy
Fixed price regardless of weight or dimensions:
- Formula: Fixed amount (e.g., $15.99)
- Use case: Predictable pricing, promotional shipping
- Example: Priority Mail Flat Rate boxes
4. Zone-Based Strategy
Price varies by distance between origin and destination:
- Formula:
weight × zone_multiplier - Use case: Long-distance vs short-distance shipments
- Example: Zone 1 (local): $0.40/lb, Zone 8 (cross-country): $1.20/lb
Benefits of Strategy Pattern
- Open/Closed Principle: Add new strategies without modifying existing code
- Single Responsibility: Each strategy handles one algorithm
- Flexibility: Swap algorithms at runtime
- Testability: Test each strategy independently
- Code Reuse: Strategies can be shared across different contexts
- Eliminates Conditionals: Replace complex if/else with polymorphism
When to Use Strategy Pattern
Use the Strategy pattern when:
- You have multiple algorithms for a specific task
- You need to switch algorithms at runtime
- You want to isolate algorithm implementation details
- You have many conditional statements selecting algorithms
- Related classes differ only in behavior
When Not to Use Strategy Pattern
Avoid the Strategy pattern when:
- You only have one or two algorithms
- Algorithms rarely change
- The overhead of creating multiple classes isn't justified
- Clients must understand differences between strategies (increases complexity)
Real-World Examples
1. Payment Processing
typescript1// Different payment strategies: credit card, PayPal, crypto2paymentProcessor.setStrategy(new CreditCardStrategy());3paymentProcessor.processPayment(amount);
2. Sorting Algorithms
typescript1// Different sorting strategies: quicksort, mergesort, heapsort2sorter.setStrategy(new QuickSortStrategy());3sorter.sort(data);
3. Compression Algorithms
typescript1// Different compression strategies: ZIP, GZIP, BZIP22compressor.setStrategy(new GzipStrategy());3compressor.compress(file);
4. Route Optimization
typescript1// Different routing strategies: fastest, shortest, scenic2navigator.setStrategy(new FastestRouteStrategy());3navigator.calculateRoute(origin, destination);
Key Takeaways
- Strategy Pattern encapsulates interchangeable algorithms
- Eliminates conditional logic by using polymorphism
- Enables runtime flexibility to switch algorithms dynamically
- Promotes code reusability and maintainability
- Perfect for logistics where different pricing, routing, or optimization strategies are needed
In the next lesson, we'll implement the Strategy pattern in TypeScript and see how to use it with dependency injection for maximum flexibility.