lesson

Introduction to Strategy Pattern

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:

typescript
1class ShippingCalculator {
2 calculateRate(
3 shipment: Shipment,
4 method: string
5 ): 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.destination
17 );
18 return shipment.weight * zoneMultiplier;
19 }
20 throw new Error('Unknown method');
21 }
22}

Problems with this Approach

  1. Violates Open/Closed Principle: Adding new strategies requires modifying existing code
  2. Difficult to Test: All strategies are coupled in one method
  3. Hard to Maintain: Large conditional blocks become unreadable
  4. No Runtime Flexibility: Can't easily swap strategies dynamically
  5. Violates Single Responsibility: One class handles multiple algorithms

The Solution: Strategy Pattern

The Strategy pattern solves these issues by:

  1. Defining a Strategy Interface: All algorithms implement the same interface
  2. Creating Concrete Strategies: Each algorithm is encapsulated in its own class
  3. Context Class: Uses a strategy without knowing implementation details
  4. 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 │ creates
8
9┌─────────────────────────────────────────────────────────────┐
10│ Context │
11│ ┌──────────────────────────────────────────────┐ │
12│ │ - strategy: RateStrategy │ │
13│ │ + setStrategy(strategy: RateStrategy): void │ │
14│ │ + calculateRate(shipment: Shipment): number │ │
15│ └──────────────────────────────────────────────┘ │
16└─────────────────────────────────────────────────────────────┘
17
18 │ delegates to
19
20┌─────────────────────────────────────────────────────────────┐
21│ <<interface>> │
22│ RateStrategy │
23│ ┌──────────────────────────────────────────────┐ │
24│ │ + calculate(shipment: Shipment): number │ │
25│ └──────────────────────────────────────────────┘ │
26└─────────────────────────────────────────────────────────────┘
27
28 │ implements
29 ┌───────────────┼───────────────┬──────────────┐
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

  1. Open/Closed Principle: Add new strategies without modifying existing code
  2. Single Responsibility: Each strategy handles one algorithm
  3. Flexibility: Swap algorithms at runtime
  4. Testability: Test each strategy independently
  5. Code Reuse: Strategies can be shared across different contexts
  6. 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

typescript
1// Different payment strategies: credit card, PayPal, crypto
2paymentProcessor.setStrategy(new CreditCardStrategy());
3paymentProcessor.processPayment(amount);

2. Sorting Algorithms

typescript
1// Different sorting strategies: quicksort, mergesort, heapsort
2sorter.setStrategy(new QuickSortStrategy());
3sorter.sort(data);

3. Compression Algorithms

typescript
1// Different compression strategies: ZIP, GZIP, BZIP2
2compressor.setStrategy(new GzipStrategy());
3compressor.compress(file);

4. Route Optimization

typescript
1// Different routing strategies: fastest, shortest, scenic
2navigator.setStrategy(new FastestRouteStrategy());
3navigator.calculateRoute(origin, destination);

Key Takeaways

  1. Strategy Pattern encapsulates interchangeable algorithms
  2. Eliminates conditional logic by using polymorphism
  3. Enables runtime flexibility to switch algorithms dynamically
  4. Promotes code reusability and maintainability
  5. 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.

Introduction to Strategy Pattern - Anko Academy