Have you ever faced a situation where you needed to switch between different methods to solve a problem? For example, imagine you are building a payment system for an online store. You might want to allow users to pay with a credit card, PayPal, or UPI. How do you design your code so that it’s easy to add new payment methods in the future without changing much of the existing code? This is where the Strategy Pattern comes in handy!
What is the Strategy Pattern?
The Strategy Pattern is a way of organizing code to make it more flexible and adaptable. Its’s a behavioural design pattern which allows you to write interchangable code. In simpler terms, it’s like having a toolbox with different tools for different tasks. You can pick the right tool (or strategy) when you need it, and you can easily swap tools without changing the toolbox itself.
Before Using the Strategy Pattern
This is an example for using multiple if-else conditions to implement the payment integration written in swift. Each payment method might have its own set of conditions and logic scattered throughout the codebase. Adding a new payment method would require modifying existing code, increasing the risk of introducing bugs. This is often messy, hard to maintain and hard to extend code.
// Handling payments without the Strategy Pattern
func processPayment(amount: Double, paymentMethod: String) -> String {
var result = ""
if paymentMethod == "CreditCard" {
// Credit card payment processing logic here
result = "Processing credit card payment of \(amount) dollars"
} else if paymentMethod == "PayPal" {
// PayPal payment processing logic here
result = "Processing PayPal payment of \(amount) dollars"
} else if paymentMethod == "UPI" {
// UPI payment processing logic here
result = "Processing UPI payment of \(amount) dollars"
}
return result
}
Let’s see how this code can be modified to use Strategy Pattern:
1. Define the Strategy Protocol
First, we need a common protocol that all payment methods will conform to.
protocol PaymentStrategy {
func processPayment(amount: Double) -> String
}
2. Implement Concrete Strategies
Next, we create classes for each payment method, conforming to the PaymentStrategy
protocol and implementing the processPayment
method.
class CreditCardPayment: PaymentStrategy {
func processPayment(amount: Double) -> String {
// Credit card payment processing logic here
return "Processing credit card payment of \(amount) dollars"
}
}
class PayPalPayment: PaymentStrategy {
func processPayment(amount: Double) -> String {
// PayPal payment processing logic here
return "Processing PayPal payment of \(amount) dollars"
}
}
class UPIMobilePayment: PaymentStrategy {
func processPayment(amount: Double) -> String {
// UPI payment processing logic here
return "Processing UPI payment of \(amount) dollars"
}
}
3. Create the Context Class
The context class uses a strategy object. It allows changing the strategy at runtime.
class PaymentProcessor {
var paymentStrategy: PaymentStrategy
init(paymentStrategy: PaymentStrategy) {
self.paymentStrategy = paymentStrategy
}
func setPaymentStrategy(paymentStrategy: PaymentStrategy) {
self.paymentStrategy = paymentStrategy
}
func processPayment(amount: Double) -> String {
return paymentStrategy.processPayment(amount: amount)
}
}
4. Use the Strategy Pattern
Now, let’s see how we can use these classes to process payments.
let amount: Double = 100
let paymentProcessor = PaymentProcessor(paymentStrategy: CreditCardPayment())
print(paymentProcessor.processPayment(amount: amount)) // Outputs: Processing credit card payment of 100 dollars
paymentProcessor.setPaymentStrategy(paymentStrategy: PayPalPayment())
print(paymentProcessor.processPayment(amount: amount)) // Outputs: Processing PayPal payment of 100 dollars
paymentProcessor.setPaymentStrategy(paymentStrategy: UPIMobilePayment())
print(paymentProcessor.processPayment(amount: amount)) // Outputs: Processing UPI payment of 100 dollars
Before using the strategy pattern:
func processPayment(amount: Double, paymentMethod: String) -> String {
var result = ""
if paymentMethod == "CreditCard" {
// Credit card payment processing logic here
result = "Processing credit card payment of \(amount) dollars"
} else if paymentMethod == "PayPal" {
// PayPal payment processing logic here
result = "Processing PayPal payment of \(amount) dollars"
} else if paymentMethod == "UPI" {
// UPI payment processing logic here
result = "Processing UPI payment of \(amount) dollars"
}
return result
}
After using the strategy pattern:
protocol PaymentStrategy {
func processPayment(amount: Double) -> String
}
class CreditCardPayment: PaymentStrategy {
func processPayment(amount: Double) -> String {
// Credit card payment processing logic here
return "Processing credit card payment of \(amount) dollars"
}
}
class PayPalPayment: PaymentStrategy {
func processPayment(amount: Double) -> String {
// PayPal payment processing logic here
return "Processing PayPal payment of \(amount) dollars"
}
}
class UPIMobilePayment: PaymentStrategy {
func processPayment(amount: Double) -> String {
// UPI payment processing logic here
return "Processing UPI payment of \(amount) dollars"
}
}
class PaymentProcessor {
var paymentStrategy: PaymentStrategy
init(paymentStrategy: PaymentStrategy) {
self.paymentStrategy = paymentStrategy
}
func setPaymentStrategy(paymentStrategy: PaymentStrategy) {
self.paymentStrategy = paymentStrategy
}
func processPayment(amount: Double) -> String {
return paymentStrategy.processPayment(amount: amount)
}
}
Why Use the Strategy Pattern?
- Flexibility: Apps can easily switch between different payment methods at runtime based on user preference.
- Maintainability: Each payment method is encapsulated in its own class, making the code easier to understand and maintain.
- Extensibility: Adding new payment methods is straightforward and does not require changes to the existing system.
When Not to Use the Strategy Pattern
While the Strategy Pattern is a powerful for certain scenarios, it is not always the best choice. Here are some points to consider when deciding not to use the Strategy Pattern:
- Simple Logic: If the algorithm or logic you need to implement is simple and unlikely to change, the overhead of implementing the Strategy Pattern might be unnecessary.
- Few Variations: When there are only a couple of variations of the algorithm, creating multiple strategy classes might add unnecessary complexity.
- Performance Concerns: The Strategy Pattern can introduce additional layers of abstraction and indirection, which might affect performance. If performance is critical and the overhead is significant, this pattern might not be suitable.
In short, strategy Pattern is a powerful tool in the arsenal of software developers. By understanding and applying this pattern, you can write cleaner, more modular, and more maintainable code.