Strategy pattern in C#

 Hello, fellow C# lovers! 😊

Today I want to share with you one of the most useful and versatile design patterns: the Strategy pattern. 🚀

The Strategy pattern is a behavioral pattern that allows you to define a family of algorithms, encapsulate each one, and make them interchangeable. This way, you can select an algorithm at runtime without changing the code that uses it. 🙌

Sounds awesome, right? But how does it work in practice? Let me show you an example in C#. 👇

Suppose you have a class called Order that represents an online purchase. You want to be able to process this order with different payment methods, such as credit card, PayPal, or Bitcoin. 💳

One way to do this is to use a switch statement inside the ProcessPayment method of the Order class. Something like this:


public class Order
{
    public decimal Amount { get; set; }

    public void ProcessPayment(string paymentMethod)
    {
        switch (paymentMethod)
        {
            case "CreditCard":
                // Process payment with credit card
                break;
            case "PayPal":
                // Process payment with PayPal
                break;
            case "Bitcoin":
                // Process payment with Bitcoin
                break;
            default:
                throw new ArgumentException("Invalid payment method");
        }
    }
}

However, this approach has some drawbacks. 😕

First of all, it violates the open-closed principle, which states that classes should be open for extension but closed for modification. If you want to add a new payment method, you have to modify the ProcessPayment method and add a new case statement. This increases the complexity and the risk of introducing bugs. 🐛

Secondly, it couples the Order class with the specific implementation details of each payment method. This makes the code less reusable and less testable. 😢

Thirdly, it makes the ProcessPayment method too long and too hard to read. It mixes different levels of abstraction and responsibilities. 😵

So how can we improve this design? By using the Strategy pattern! 😍

The Strategy pattern suggests that we extract the payment logic into separate classes that implement a common interface. This interface defines an abstract method that takes an Order as a parameter and performs the payment operation. Let’s call this interface IPaymentStrategy. 🔥


public interface IPaymentStrategy
{
    void Pay(Order order);
}

Then we can create concrete classes that implement this interface for each payment method. For example, here is the CreditCardPaymentStrategy class:


public class CreditCardPaymentStrategy : IPaymentStrategy
{
    public void Pay(Order order)
    {
        // Process payment with credit card
    }
}

Similarly, we can create PayPalPaymentStrategy and BitcoinPaymentStrategy classes. 🙃

Now we can modify the Order class to use an IPaymentStrategy instance instead of a switch statement. We can pass this instance as a constructor parameter or as a setter property. We can also create a default constructor that uses a default strategy (for example, credit card). Here is how the Order class looks like now:


public class Order
{
    public decimal Amount { get; set; }

    private IPaymentStrategy _paymentStrategy;

    public Order(IPaymentStrategy paymentStrategy)
    {
        _paymentStrategy = paymentStrategy;
    }

    public Order() : this(new CreditCardPaymentStrategy())
    {
    }

    public void SetPaymentStrategy(IPaymentStrategy paymentStrategy)
    {
        _paymentStrategy = paymentStrategy;
    }

    public void ProcessPayment()
    {
        _paymentStrategy.Pay(this);
    }
}

As you can see, the ProcessPayment method is now much simpler and cleaner. It just delegates the payment operation to the current strategy object. 🎉

This way, we can easily change the payment behavior at runtime by passing a different strategy object to the Order instance. 


For example:


var order = new Order();
order.Amount = 100m;
order.ProcessPayment(); // Pays with credit card by default

order.SetPaymentStrategy(new PayPalPaymentStrategy());
order.ProcessPayment(); // Pays with PayPal

order.SetPaymentStrategy(new BitcoinPaymentStrategy());
order.ProcessPayment(); // Pays with Bitcoin


Isn’t that amazing? 😎


By using the Strategy pattern, we have achieved several benefits:

  • We have followed the open-closed principle. We can add new payment methods without changing the existing code. We just need to create a new class that implements the IPaymentStrategy interface and pass it to the Order instance.
  • We have decoupled the Order class from the specific payment logic. This makes the code more reusable and more testable.
  • We have improved the readability and maintainability of the code. The ProcessPayment method is now concise and clear.
  • We have increased the flexibility and extensibility of the code. We can switch between different strategies at runtime depending on our needs.

Of course, there are some trade-offs as well. For example:

  • We have introduced more classes and interfaces into our design. This may increase the complexity and memory usage of our application.
  • We have added an extra level of indirection between the Order class and the payment logic. This may affect the performance and debugging of our application.


As always, you should use your judgment and experience to decide when to apply this pattern and when not to. There is no one-size-fits-all solution in software design. 😉


I hope you enjoyed this article and learned something new about the Strategy pattern in C#. If you want to learn more about this pattern or other design patterns in C#, I recommend you check out these resources:

  • Design Patterns in C# by Steven John Metsker: A great book that covers 23 classic design patterns with clear examples and explanations in C#.
  • Refactoring Guru: A fantastic website that provides detailed descriptions and illustrations of various design patterns with code examples in multiple languages, including C#.
  • Dot Net Tricks: A helpful blog that explains how to implement different design patterns in .NET with real-world scenarios and examples.

You can find the code examples here: source code

Thank you for reading and happy coding! 😊👋

Comments

Popular posts from this blog

Which GOF patterns are good for C#?

Angular on a regular SharePoint page (part 2)

Chain of Responsibility pattern in C#