π§ Introduction
The Strategy pattern is a behavioral design pattern that allows you to define multiple ways to perform an operation by encapsulating them in separate classes. This makes behaviors interchangeable, following the Open/Closed Principle: open for extension but closed for modification.
π Why itβs useful and in what scenarios itβs applied.
This pattern is useful when an application needs to perform the same operation using different approaches. Instead of cluttering your code with long if-else or switch statements, you encapsulate each variation in its own class, making the code more maintainable and scalable.
π΅βπ« Common misconceptions or confusion around it.
-
Strategy vs. Factory Pattern
- βMisconception: "Strategy and Factory are the same."
- β
Reality: The Factory Pattern is about creating objects, while the Strategy Pattern is about choosing a behavior at runtime.
- You can use a Factory to instantiate the correct Strategy, but they serve different purposes.
-
Thinking It's Only for Algorithms
- βMisconception: "Strategy is only for sorting or mathematical algorithms."
- β
Reality: It applies to any scenario where behavior varies dynamically, such as:
- Payment methods π°
- Logging mechanisms π¨βπ»
- Compression formats (ZIP, GZIP, etc.) ποΈ
-
Overcomplicating Small Problems
- βMisconception: "Strategy should be used everywhere!"
- β
Reality: If you only have two or three simple cases, a basic function or if-else might be enough.
- Good Use: Multiple payment gateways with different API calls.
- Bad Use: A function that just switches between two database queries.
-
Forgetting Dependency Injection
- βMisconception: "Each Strategy should be instantiated manually."
- β Reality: Itβs better to use Dependency Injection or a Factory Pattern to select and provide the strategy dynamically.
π Problem Example
You launch an app in Mexico, and it quickly becomes a hit! π At first, you integrate Conekta, a popular Mexican payment gateway.
As time goes on, users start requesting PayPal, so you add support for it. Then, your app gains traction in the USA, and you integrate Stripe to accept USD payments.
Later, your app expands to Colombia, where you realize that using a local payment gateway reduces transaction fees compared to Stripe. To cut costs and keep users happy, you integrate yet another payment provider.
Your app is a commercial success... but your code is a nightmare. π± Maintaining multiple payment gateway integrations has become a tangled mess, full of if-else statements and duplicated logic.
π‘ Solution: The Strategy Pattern
Instead of hardcoding payment logic, the Strategy Pattern offers a cleaner approach:
β
Encapsulate payment algorithms in separate classes, called strategies. Each class represents a different payment gateway (e.g., ConektaStrategy
, PayPalStrategy
, StripeStrategy
).
β
Create a Context
class that decides which strategy to use at runtime. The context doesn't need to know the details of each payment methodβit simply delegates the work to the selected strategy.
β Define a common interface for all payment strategies. This ensures consistency in your code and makes it easy to add new payment gateways in the future
Hereβs a TypeScript example demonstrating the Strategy Pattern for handling multiple payment gateways dynamically:
π¨βπ» Code Example
Step 1: Define a Common Interface
Each payment method should follow the same structure.
// PaymentStrategy.ts
export interface PaymentStrategy {
pay(amount: number): void;
}
Step 2: Create Payment Strategies
Each payment gateway gets its own class implementing PaymentStrategy
.
// ConektaStrategy.ts
import { PaymentStrategy } from "./PaymentStrategy";
export class ConektaStrategy implements PaymentStrategy {
pay(amount: number): void {
console.log(`Processing payment of $${amount} MXN via Conekta.`);
}
}
// PayPalStrategy.ts
import { PaymentStrategy } from "./PaymentStrategy";
export class PayPalStrategy implements PaymentStrategy {
pay(amount: number): void {
console.log(`Processing payment of $${amount} USD via PayPal.`);
}
}
// StripeStrategy.ts
import { PaymentStrategy } from "./PaymentStrategy";
export class StripeStrategy implements PaymentStrategy {
pay(amount: number): void {
console.log(`Processing payment of $${amount} USD via Stripe.`);
}
}
// ColombianGatewayStrategy.ts
import { PaymentStrategy } from "./PaymentStrategy";
export class ColombianGatewayStrategy implements PaymentStrategy {
pay(amount: number): void {
console.log(`Processing payment of $${amount} COP via Colombian Gateway.`);
}
}
Step 3: Implement the Context Class
This class selects the payment strategy at runtime.
// PaymentContext.ts
import { PaymentStrategy } from "./PaymentStrategy";
export class PaymentContext {
private strategy: PaymentStrategy;
constructor(strategy: PaymentStrategy) {
this.strategy = strategy;
}
setStrategy(strategy: PaymentStrategy): void {
this.strategy = strategy;
}
processPayment(amount: number): void {
this.strategy.pay(amount);
}
}
Step 4: Use the Strategy Pattern in Your App
Now, we can dynamically switch payment methods:
// main.ts
import { PaymentContext } from "./PaymentContext";
import { ConektaStrategy } from "./ConektaStrategy";
import { PayPalStrategy } from "./PayPalStrategy";
import { StripeStrategy } from "./StripeStrategy";
import { ColombianGatewayStrategy } from "./ColombianGatewayStrategy";
// Select initial payment method
const paymentContext = new PaymentContext(new ConektaStrategy());
paymentContext.processPayment(500); // Processing payment of $500 MXN via Conekta.
// Switch to PayPal
paymentContext.setStrategy(new PayPalStrategy());
paymentContext.processPayment(100); // Processing payment of $100 USD via PayPal.
// Switch to Stripe
paymentContext.setStrategy(new StripeStrategy());
paymentContext.processPayment(200); // Processing payment of $200 USD via Stripe.
// Switch to Colombian Gateway
paymentContext.setStrategy(new ColombianGatewayStrategy());
paymentContext.processPayment(300000); // Processing payment of $300000 COP via Colombian Gateway.
π Benefits of This Approach
β
Scalability: Easily add new payment gateways without modifying existing code.
β
Clean Code: No more long if-else
statements for payment processing.
β
Flexibility: Switch payment methods dynamically at runtime.
Would you like me to add any refinements, such as using dependency injection or a factory to select the strategy automatically? π
π€ Conclusion
The Strategy Pattern is a powerful tool for managing complex or varying behaviors in your application. In the context of payment gateways, it allows you to encapsulate different payment methods in separate classes, making your code more maintainable and scalable. By using the Context class to delegate the logic and Dependency Injection (DI) to manage your strategies, you ensure that your app is flexible enough to handle future changes or new payment methods seamlessly.
Remember, the Strategy Pattern helps avoid long chains of if-else
or switch
statements, making your code cleaner and easier to extend. Whether youβre building a payment processing system or dealing with other dynamic behaviors, this pattern can simplify your design and reduce code duplication.
π Let me know your thoughts
What do you think? Have you implemented the Strategy Pattern in your own projects? If so, how did it help simplify your code? Feel free to share your thoughts or any questions you might have about this pattern in the comments below!
Letβs keep the conversation going! π