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: Declares an interface common to all supported algorithms.
- Concrete Strategies: Implement different variations of an algorithm.
- Context: Maintains a reference to a Strategy object and defines an interface to let the strategy access its data.
- Client: Creates a specific strategy object and passes it to the context.
When to Use
Use the Strategy Pattern when:
- You need to use different variants of an algorithm within an object and be able to switch them at runtime.
- You have multiple classes that differ only in their behavior.
- You want to isolate the implementation details of an algorithm from the code that uses it.
- You need to hide complex, algorithm-specific data structures from clients.
Benefits
- Algorithms can be switched at runtime.
- Isolation of implementation details from the code that uses them.
- Replacement of inheritance with composition.
- Open/Closed Principle: You can add new strategies without modifying the context.
Real-World Uses
- Sorting Algorithms: Using different sorting strategies based on data types or size.
- Payment Processing: Supporting multiple payment methods (credit card, PayPal, cryptocurrency).
- Compression Strategies: Applying different compression algorithms based on file types.
- Authentication Mechanisms: OAuth, JWT, basic authentication strategies.
- Tax Calculation: Different strategies for various geographical regions.
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
Payment Method (Strategy)
Select a payment method to use for checkout: