Strategy Design Pattern in .NET

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.