Strategy Design Pattern
The Strategy Design Pattern is a Behavioral design pattern that defines a family of algorithms, encapsulating each of them in a dedicated class, and making them interchangeable. With that, they ctan be replaced or modified independently of the client code that uses it, promoting code reuse, flexibility, and easier maintenance. This is one of the Gang of Four (GoF) Design Patterns and is commonly used in many projects. In this article, I present how to implement this pattern.
This pattern allows you to extract the behavior of an object into separate classes that can be selected/switched at runtime. Some classes that represent different strategies are created, as well as a context class whose behavior varies according to its strategy class. For example, consider a payment scenario where you can have different types of payment methods. In this case, you can have a PaymentContext class and a series of classes representing different payment methods (Credit card, PayPal, etc).
For demonstration purposes, I created a console application with .NET 8. which will print a message saying which payment method was executed.
Implementing the Strategy Design Pattern
The first step to implement this pattern is to create the strategy interface, which defines the contract for all concrete payment classes. This is the IPaymentStrategy
:
public interface IPaymentStrategy
{
void ProcessPayment(double amount);
}
Next, we create the Context class, which in this example, is the PaymentContext
:
public class PaymentContext
{
private IPaymentStrategy _payment;
public PaymentContext(IPaymentStrategy payment)
{
_payment = payment;
}
public void ProcessPayment(double amount)
{
_payment.ProcessPayment(amount);
}
public void SetStrategy(IPaymentStrategy payment)
{
_payment = payment;
}
}
This class contains
- The constructor, which receives the
IPaymentStrategy
interface.
- A private property of type
IPaymentStrategy
- The
ProcessPayment
method, which executes the ProcessPayment.
- The
SetStrategy
method that can be used when you need to switch between payment methods dynamically at runtime (based on user input or other runtime conditions, for example), allowing you to achieve that without needing to create new PaymentContext instances — in case you don’t need to have this behavior, you do not need to implement this method.
Now we can create concrete payment strategies that implement the IPaymentStrategy
interface. For this demo, I created these three classes:
public class CreditCardPayment : IPaymentStrategy
{
public void ProcessPayment(double amount)
{
Console.WriteLine($"Processing Credit Card payment of {amount} euros.");
}
}
public class PayPalPayment : IPaymentStrategy
{
public void ProcessPayment(double amount)
{
Console.WriteLine($"Processing PayPal payment of {amount} euros.");
}
}
public class BitcoinPayment : IPaymentStrategy
{
public void ProcessPayment(double amount)
{
Console.WriteLine($"Processing Bitcoin payment of {amount} euros.");
}
}
Now you can initialize a new PaymentContext
instance with the associated payment method class:
static void Example()
{
PaymentContext paymentContext;
double amount = 100.0;
paymentContext = new PaymentContext(new CreditCardPayment());
paymentContext.ProcessPayment(amount);
paymentContext = new PaymentContext(new PayPalPayment());
paymentContext.ProcessPayment(amount);
paymentContext = new PaymentContext(new BitcoinPayment());
paymentContext.ProcessPayment(amount);
}
- On line 3, the variable for the PaymentContext is defined.
- On line 4, the variable for the amount that is going to be used for the payment is defined.
- On line 7, a new instance of
PaymentContext
class is created, and it is associated with a concrete payment strategy for Credit Card, and on line 8 the ProcessPayment method is executed.
- On lines 11 and 15, a new instance of
PaymentContext
class is created for PayPal and Bitcoin payment methods.
Output of this method
Processing Credit Card payment of 100 euros.
Processing PayPal payment of 100 euros.
Processing Bitcoin payment of 100 euros.
It’s also possible to achieve the same result without creating multiple instances of the PaymentContext
. For that, you can use the SetStrategy
method that was previously created. For example:
static void ExampleWithSetStrategy()
{
PaymentContext paymentContext;
double amount = 100.0;
paymentContext = new PaymentContext(new CreditCardPayment());
paymentContext.ProcessPayment(amount);
paymentContext.SetStrategy(new PayPalPayment());
paymentContext.ProcessPayment(amount);
paymentContext.SetStrategy(new BitcoinPayment());
paymentContext.ProcessPayment(amount);
}
- On lines 3 and 4, the variables are defined.
- On line 7, a new instance
PaymentContext
is created, associated with a concrete payment strategy for Credit Card (similar to the previous example).
- On lines 11 and 15, instead of creating a new instance
PaymentContext
, it is now switching the payment method strategy for PayPal and Bitcoin without creating a new instance of the class.
Output of this method
Processing Credit Card payment of 100 euros.
Processing PayPal payment of 100 euros.
Processing Bitcoin payment of 100 euros.
Conclusion
With the Strategy pattern, you can encapsulate your code into separate classes and make them interchangeable at runtime, promoting flexibility, modularity, and code reusability. This pattern applies the Single Responsibility and the Open-Closed Principles from the SOLID principles, as demonstrated in this example. If you need to perform some change in a specific payment method, you only need to change the specific class, and in case you need to add a new payment method, you don’t need to touch the existing code. Instead, you are going to create a new payment method class.