22 December 2017
Introduction
- In the previous post, we explored the Observer Pattern, where multiple objects (observers) are notified about changes in the state of another object (subject). This time, we’re diving into another key behavioral design pattern: the Strategy Pattern.
- While the Observer Pattern is about communication between objects, the Strategy Pattern is all about changing behavior dynamically at runtime. It allows you to define a family of algorithms (strategies) and make them interchangeable, allowing the client to choose the right algorithm for the task at hand.
- By the end of this post, you’ll understand how the Strategy Pattern works, when to use it, and how to implement it effectively in C#.
What Is the Strategy Pattern?
- Definition: The Strategy Pattern is a behavioral design pattern that allows an object to choose a specific algorithm or strategy to use at runtime, instead of hardcoding that algorithm into the object.
- Use Case:
- Consider an e-commerce platform that supports different payment methods (credit card, PayPal, etc.). The payment processing logic for each method is different, but the platform needs to handle all of them in a consistent way. Using the Strategy Pattern, we can define separate strategies for each payment method, and switch between them dynamically without modifying the payment processing code.
When to Use the Strategy Pattern
- Use Cases:
- Multiple Algorithms: When you have multiple ways of performing a task, and you need to choose one at runtime based on specific conditions.
- Open/Closed Principle: If your code needs to be open for extension but closed for modification, the Strategy Pattern allows you to add new strategies without changing the existing code.
- Simplifying Conditional Statements: If your code has complex conditional statements to select which algorithm to use, the Strategy Pattern can cleanly eliminate those conditionals.
How the Strategy Pattern Works
- The Strategy Pattern consists of three main components:
- Strategy Interface: Defines a common interface for all supported algorithms (strategies).
- Concrete Strategy: Implements the algorithm defined in the strategy interface.
- Context: Maintains a reference to a specific strategy and delegates the task to the current strategy.
- The key advantage is that the context class doesn’t need to know the specifics about the algorithm, and it can switch between different strategies as needed.
Implementing the Strategy Pattern in C#
Let’s now implement a simple example of the Strategy Pattern. In this case, we will create a payment processing system that supports multiple payment methods. Each payment method is a different strategy, and the system can dynamically choose the right one.
using System;
// Strategy Interface
public interface IPaymentStrategy
{
void Pay(decimal amount);
}
// Concrete Strategies
public class CreditCardPayment : IPaymentStrategy
{
public void Pay(decimal amount)
{
Console.WriteLine($"Paid {amount:C} using Credit Card.");
}
}
public class PayPalPayment : IPaymentStrategy
{
public void Pay(decimal amount)
{
Console.WriteLine($"Paid {amount:C} using PayPal.");
}
}
public class BankTransferPayment : IPaymentStrategy
{
public void Pay(decimal amount)
{
Console.WriteLine($"Paid {amount:C} via Bank Transfer.");
}
}
// Context Class
public class ShoppingCart
{
private IPaymentStrategy _paymentStrategy;
// Set the strategy dynamically at runtime
public void SetPaymentStrategy(IPaymentStrategy paymentStrategy)
{
_paymentStrategy = paymentStrategy;
}
// Process the payment using the current strategy
public void Checkout(decimal amount)
{
_paymentStrategy.Pay(amount);
}
}
public class Program
{
public static void Main()
{
var shoppingCart = new ShoppingCart();
// Use Credit Card as the payment method
shoppingCart.SetPaymentStrategy(new CreditCardPayment());
shoppingCart.Checkout(150.00m);
// Use PayPal as the payment method
shoppingCart.SetPaymentStrategy(new PayPalPayment());
shoppingCart.Checkout(250.00m);
// Use Bank Transfer as the payment method
shoppingCart.SetPaymentStrategy(new BankTransferPayment());
shoppingCart.Checkout(1000.00m);
}
}
Explanation of Code
- IPaymentStrategy Interface: This defines the common method
Pay(decimal amount)
that all payment methods (strategies) must implement. - Concrete Strategies: These are the different payment methods. Each class implements the
IPaymentStrategy
interface and provides a specific implementation for thePay()
method. - ShoppingCart (Context): The
ShoppingCart
class represents the context in which the payment strategies are used. It has a methodSetPaymentStrategy()
that allows the payment strategy to be set dynamically. TheCheckout()
method delegates the payment task to the currently set strategy. - Main() Method: In the
Main()
method, we create aShoppingCart
object and set the payment strategy dynamically for each checkout process. Depending on the user’s choice, the appropriate payment method is used.
Best Practices for the Strategy Pattern
- Encapsulate Algorithms: The Strategy Pattern is ideal when you have different algorithms for the same task. By encapsulating each algorithm in its own class, you improve code readability and maintainability.
- Avoid Overuse: While the Strategy Pattern can be useful, it’s essential to avoid overusing it. If you only have a few strategies or the logic is unlikely to change, the overhead of adding the pattern may not be necessary.
- Favor Composition Over Inheritance: This pattern promotes composition over inheritance. Instead of having a large inheritance tree with different classes implementing different behaviors, you can use interfaces and composition to achieve more flexibility.
Conclusion
- The Strategy Pattern allows you to define a family of algorithms and makes them interchangeable at runtime. This pattern helps you avoid complex conditionals in your code and allows you to easily extend functionality by adding new strategies without modifying the existing code.
- Whether you’re building an e-commerce system with multiple payment methods, or a complex application where different behaviors need to be selected dynamically, the Strategy Pattern provides a clean and flexible solution.