Learning Objectives
- Understand the Strategy pattern concept
- Define interchangeable algorithms
- Switch strategies at runtime
- Eliminate conditional logic
- Apply strategy best practices
What is the Strategy Pattern?
Strategy allows you to select an algorithm at runtime, avoiding complex conditional logic. Each algorithm is encapsulated in its own class or function, making them interchangeable.
Basic Example
// Define strategies
const strategies = {
credit: (amount) => {
console.log(`Processing $${amount} via credit card`);
return { success: true, method: 'credit', fee: amount * 0.03 };
},
paypal: (amount) => {
console.log(`Processing $${amount} via PayPal`);
return { success: true, method: 'paypal', fee: amount * 0.025 };
},
crypto: (amount) => {
console.log(`Processing $${amount} via cryptocurrency`);
return { success: true, method: 'crypto', fee: amount * 0.01 };
}
};
class PaymentProcessor {
constructor(strategy) {
this.strategy = strategy;
}
setStrategy(strategy) {
this.strategy = strategy;
}
process(amount) {
return this.strategy(amount);
}
}
// Usage - switch strategies at runtime
const processor = new PaymentProcessor(strategies.credit);
processor.process(100);
// Processing $100 via credit card
processor.setStrategy(strategies.paypal);
processor.process(50);
// Processing $50 via PayPal
processor.setStrategy(strategies.crypto);
processor.process(200);
// Processing $200 via cryptocurrency
Real-World Examples
Validation Strategies
class Validator {
constructor(strategy) {
this.strategy = strategy;
}
setStrategy(strategy) {
this.strategy = strategy;
}
validate(data) {
return this.strategy.validate(data);
}
}
const emailStrategy = {
validate(email) {
const isValid = /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
return {
valid: isValid,
message: isValid ? 'Valid email' : 'Invalid email format'
};
}
};
const phoneStrategy = {
validate(phone) {
const isValid = /^\d{10}$/.test(phone);
return {
valid: isValid,
message: isValid ? 'Valid phone' : 'Phone must be 10 digits'
};
}
};
const passwordStrategy = {
validate(password) {
const isValid = password.length >= 8 && /[A-Z]/.test(password) && /[0-9]/.test(password);
return {
valid: isValid,
message: isValid ? 'Valid password' : 'Password must be 8+ chars with uppercase and number'
};
}
};
// Usage
const validator = new Validator(emailStrategy);
console.log(validator.validate('test@example.com')); // { valid: true, ... }
validator.setStrategy(phoneStrategy);
console.log(validator.validate('1234567890')); // { valid: true, ... }
validator.setStrategy(passwordStrategy);
console.log(validator.validate('Password123')); // { valid: true, ... }
Sorting Strategies
class Sorter {
constructor(strategy) {
this.strategy = strategy;
}
sort(data) {
return this.strategy.sort([...data]);
}
}
const bubbleSortStrategy = {
sort(arr) {
console.log('Using bubble sort');
for (let i = 0; i < arr.length; i++) {
for (let j = 0; j < arr.length - 1 - i; j++) {
if (arr[j] > arr[j + 1]) {
[arr[j], arr[j + 1]] = [arr[j + 1], arr[j]];
}
}
}
return arr;
}
};
const quickSortStrategy = {
sort(arr) {
console.log('Using quick sort');
if (arr.length <= 1) return arr;
const pivot = arr[arr.length - 1];
const left = arr.filter((x, i) => x <= pivot && i < arr.length - 1);
const right = arr.filter(x => x > pivot);
return [...this.sort(left), pivot, ...this.sort(right)];
}
};
// Usage
const data = [5, 2, 8, 1, 9];
const sorter = new Sorter(bubbleSortStrategy);
console.log(sorter.sort(data)); // Using bubble sort: [1, 2, 5, 8, 9]
sorter.strategy = quickSortStrategy;
console.log(sorter.sort(data)); // Using quick sort: [1, 2, 5, 8, 9]
Compression Strategies
class Compressor {
constructor(strategy) {
this.strategy = strategy;
}
compress(data) {
return this.strategy.compress(data);
}
decompress(data) {
return this.strategy.decompress(data);
}
}
const zipStrategy = {
compress(data) {
console.log('Compressing with ZIP');
return `ZIP:${data}`;
},
decompress(data) {
return data.replace('ZIP:', '');
}
};
const gzipStrategy = {
compress(data) {
console.log('Compressing with GZIP');
return `GZIP:${data}`;
},
decompress(data) {
return data.replace('GZIP:', '');
}
};
const compressor = new Compressor(zipStrategy);
const compressed = compressor.compress('Hello World');
console.log(compressed); // ZIP:Hello World
console.log(compressor.decompress(compressed)); // Hello World
Eliminating Conditional Logic
// Bad - lots of conditionals
function calculateShipping(type, weight) {
if (type === 'standard') {
return weight * 0.5;
} else if (type === 'express') {
return weight * 1.5;
} else if (type === 'overnight') {
return weight * 3.0;
}
return 0;
}
// Good - strategy pattern
const shippingStrategies = {
standard: (weight) => weight * 0.5,
express: (weight) => weight * 1.5,
overnight: (weight) => weight * 3.0
};
class ShippingCalculator {
calculate(type, weight) {
const strategy = shippingStrategies[type];
if (!strategy) {
throw new Error(`Unknown shipping type: ${type}`);
}
return strategy(weight);
}
}
const calculator = new ShippingCalculator();
console.log(calculator.calculate('express', 10)); // 15
When to Use Strategy Pattern
Good use cases:
- Multiple algorithms for the same task
- Eliminate complex if/else or switch statements
- Runtime algorithm selection
- Payment processing methods
- Sorting/filtering/compression algorithms
- Validation rules
Benefits
- Open/Closed Principle: Add new strategies without modifying existing code
- Eliminates conditionals: No more complex if/else chains
- Runtime flexibility: Switch algorithms at runtime
- Easy to test: Each strategy can be tested independently
- Reusable: Strategies can be reused across different contexts
Best Practices
1. Use consistent interface
// All strategies should have same methods
const strategy1 = {
execute(data) { /* ... */ }
};
const strategy2 = {
execute(data) { /* ... */ }
};
2. Validate strategy exists
setStrategy(strategy) {
if (!strategy || typeof strategy.execute !== 'function') {
throw new Error('Invalid strategy');
}
this.strategy = strategy;
}
3. Use object lookup instead of switch
// Good
const strategies = {
type1: strategy1,
type2: strategy2
};
const strategy = strategies[type];
Key Takeaways
- Strategy defines interchangeable algorithms
- Select algorithm at runtime
- Eliminates complex if/else chains
- Each strategy is encapsulated
- Easy to add new strategies
- Follows Open/Closed Principle
- Makes code more maintainable and testable
- Common in payment processing, validation, sorting