Strategy Pattern in TypeScript
The Strategy Pattern is a behavioral design pattern that enables selecting an algorithmโs behavior at runtime. Instead of embedding algorithms directly into classes, we define them as separate objects (strategies) and make them interchangeable.
This pattern is widely used in scenarios like:
- Payment processing (credit card vs. PayPal)
- Sorting algorithms (quick sort vs. merge sort)
- Navigation systems (driving vs. walking directions)
In TypeScript, interfaces and classes make implementing the Strategy Pattern straightforward and type-safe.
๐น Why It Matters
- Flexibility: Change behaviors dynamically without modifying existing code.
- Maintainability: Avoid conditional statements (
if-else
/switch
) that clutter code. - Testability: Isolate strategies for easier unit testing.
- Open/Closed Principle: Extend behavior without modifying existing classes.
๐น Core Concepts
The Strategy Pattern consists of three main components:
- Context: Holds a reference to a strategy and delegates work to it.
- Strategy Interface: Defines a common method that all strategies implement.
- Concrete Strategies: Implement the interface with specific behaviors.
Source: medium
๐น Code Walkthrough
Example: Payment Processor
Weโll create a payment system that supports different payment methods (Credit Card, PayPal).
1. Define the Strategy Interface
// PaymentStrategy.ts
interface PaymentStrategy {
pay(amount: number): string; // All strategies must implement this method
}
2. Implement Concrete Strategies
// CreditCardPayment.ts
class CreditCardPayment implements PaymentStrategy {
pay(amount: number): string {
return `Paid ${amount} USD via Credit Card.`;
}
}
// PayPalPayment.ts
class PayPalPayment implements PaymentStrategy {
pay(amount: number): string {
return `Paid ${amount} USD via PayPal.`;
}
}
3. Create the Context Class
// PaymentProcessor.ts
class PaymentProcessor {
private strategy: PaymentStrategy; // Reference to the current strategy
constructor(strategy: PaymentStrategy) {
this.strategy = strategy;
}
setStrategy(strategy: PaymentStrategy): void {
this.strategy = strategy; // Dynamically switch strategies
}
executePayment(amount: number): string {
return this.strategy.pay(amount); // Delegate payment to the strategy
}
}
4. Use the Pattern
// main.ts
const creditCardPayment = new CreditCardPayment();
const payPalPayment = new PayPalPayment();
const processor = new PaymentProcessor(creditCardPayment);
console.log(processor.executePayment(100)); // Paid 100 USD via Credit Card.
processor.setStrategy(payPalPayment);
console.log(processor.executePayment(50)); // Paid 50 USD via PayPal.
๐น Common Mistakes
- Overusing the Pattern: Not every conditional needs a strategy. Use it only when behaviors change frequently.
- Tight Coupling: If strategies depend on the context, consider dependency injection.
- Performance Overhead: Creating many small strategy classes can increase complexity.
๐น Best Practices
โ
Keep strategies stateless if possible (avoid storing unnecessary data).
โ
Use dependency injection for better testability.
โ
Document strategy interfaces clearly to avoid confusion.
๐น Final Thoughts
The Strategy Pattern is a powerful tool for writing flexible, maintainable, and testable TypeScript applications. By decoupling algorithms from their clients, you can easily extend functionality without modifying existing code.
For further reading:
Happy coding! ๐