Strategy Pattern

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

Understanding the Strategy Pattern

The Strategy pattern is a behavioral design pattern that enables selecting an algorithm at runtime. It defines a family of algorithms, encapsulates each one, and makes them interchangeable. The pattern allows the algorithm to vary independently from clients that use it.

Problem

Imagine you're developing an application that needs to implement various algorithms for different operations, such as data validation, sorting, or payment processing. As your application grows, you end up with multiple conditional statements throughout your code to determine which algorithm to use based on context.

This approach makes your code hard to maintain, violates the open/closed principle, and makes it difficult to add new algorithms without modifying existing code.

Solution

The Strategy pattern suggests defining a family of algorithms, putting each one into a separate class, and making their objects interchangeable. Each algorithm becomes a "strategy" that can be selected at runtime.

This approach isolates algorithm implementation from the code that uses it, enabling you to vary the algorithm independently of the context and add new algorithms without modifying existing code.

Structure

Strategy Pattern Structure

When to Use

Use the Strategy Pattern when:

Benefits

Real-World Uses

Implementation Example

Below is a JavaScript implementation of the Strategy pattern for handling different payment methods:

// Strategy interface
class PaymentStrategy {
  pay(amount) {
    throw new Error("pay() method must be implemented");
    }
}

// Concrete strategies
class CreditCardStrategy extends PaymentStrategy {
  constructor(cardNumber, name, cvv, expirationDate) {
    super();
    this.cardNumber = cardNumber;
    this.name = name;
    this.cvv = cvv;
    this.expirationDate = expirationDate;
  }
  
  pay(amount) {
    console.log(`Paid $${amount} using Credit Card`);
    return true;
  }
}

class PayPalStrategy extends PaymentStrategy {
  constructor(email, password) {
    super();
    this.email = email;
    this.password = password;
  }
  
  pay(amount) {
    console.log(`Paid $${amount} using PayPal`);
    return true;
  }
}

class CryptoStrategy extends PaymentStrategy {
  constructor(walletAddress) {
    super();
    this.walletAddress = walletAddress;
  }
  
  pay(amount) {
    console.log(`Paid $${amount} using Cryptocurrency`);
    return true;
  }
}

// Context
class ShoppingCart {
  constructor() {
    this.items = [];
    this.paymentStrategy = null;
  }
  
  addItem(item) {
    this.items.push(item);
  }
  
  setPaymentStrategy(paymentStrategy) {
    this.paymentStrategy = paymentStrategy;
  }
  
  calculateTotal() {
    return this.items.reduce((total, item) => total + item.price, 0);
  }
  
  checkout() {
    if (!this.paymentStrategy) {
      throw new Error("No payment strategy set");
    }
    
    const amount = this.calculateTotal();
    return this.paymentStrategy.pay(amount);
  }
}

// Client code
const cart = new ShoppingCart();
cart.addItem({ name: "Product 1", price: 100 });
cart.addItem({ name: "Product 2", price: 50 });

// User chooses to pay with credit card
cart.setPaymentStrategy(new CreditCardStrategy(
  "1234 5678 9012 3456", "John Doe", "123", "12/25"
));
cart.checkout();

// User changes mind and wants to pay with PayPal
cart.setPaymentStrategy(new PayPalStrategy(
  "johndoe@example.com", "password123"
));

In this example, different payment methods are implemented as strategies. The shopping cart (context) uses a payment strategy to process payments without knowing the specific details of each payment method.

Interactive Demo

Experience the Strategy pattern in action with this interactive payment processing demo. Add items to your cart, select different payment strategies, and see how the behavior changes while the interface remains consistent.

Shopping Cart

Total: $0

Payment Method (Strategy)

Select a payment method to use for checkout: