Skip to Content
All posts

The Strategy Pattern in C#

Unraveling Behavioral Design Patterns

3 min read ·  — #design-patterns#structrural-patterns#strategy

The Strategy Pattern in C#

Introduction

In the labyrinthine world of software engineering, patterns are like Ariadne's thread: they guide us through complexity towards elegant solutions. Among these, Behavioral Design Patterns stand out for their keen focus on improving communication and responsibility distribution between objects. Today, we delve into the Strategy Pattern, a gem in the crown of Behavioral Patterns, showcasing its power and flexibility in C#/.Net applications. This exploration is tailored for software engineers with a penchant for design, those who orchestrate code into symphonies of functionality. Let's embark on a journey to unravel the mysteries of the Strategy Pattern, turning the theoretical into the tangible, and abstract concepts into real-world applications.

The Strategy Pattern Explained

At its core, the Strategy Pattern is about enabling an object, often referred to as the context, to change its behavior by swapping out algorithms or strategies at runtime. This is achieved without altering the context's interface or the client code, adhering to the open/closed principle — open for extension but closed for modification.

Imagine a scenario in a software system where multiple algorithms might be used to accomplish a task, each suitable under different circumstances. Hard-coding each algorithm into the object that needs it not only bloats its size and complexity but also petrifies its behavior, making future changes or additions a daunting task. The Strategy Pattern liberates us from these constraints by encapsulating each algorithm in its own class, all of which implement a common interface. This architectural move decouples the algorithms from their usage, fostering modularity, testability, and flexibility.

Real-World Scenario: Payment Processing System

Consider an e-commerce platform that supports multiple payment methods: credit card, PayPal, and cryptocurrency. As the platform evolves, new payment methods may emerge, and existing ones may need updates or removal. Implementing the Strategy Pattern can elegantly handle this scenario.

Step 1: Define the Strategy Interface

First, we define a common interface for all payment strategies. This interface declares a method that all concrete strategies must implement, for processing payments.

public interface IPaymentStrategy
{
    void ProcessPayment(decimal amount);
}

Step 2: Implement Concrete Strategies

Next, we implement each payment method as a class that implements the IPaymentStrategy interface.

public class CreditCardPayment : IPaymentStrategy
{
    public void ProcessPayment(decimal amount)
    {
        // Implementation for credit card payment
        Console.WriteLine($"Processing credit card payment of {amount}");
    }
}

public class PayPalPayment : IPaymentStrategy
{
    public void ProcessPayment(decimal amount)
    {
        // Implementation for PayPal payment
        Console.WriteLine($"Processing PayPal payment of {amount}");
    }
}

public class CryptoPayment : IPaymentStrategy
{
    public void ProcessPayment(decimal amount)
    {
        // Implementation for cryptocurrency payment
        Console.WriteLine($"Processing cryptocurrency payment of {amount}");
    }
}

Step 3: The Context Class

The context class holds a reference to a strategy object and provides a method to set the strategy at runtime.

public class PaymentContext
{
    private IPaymentStrategy _paymentStrategy;

    // Constructor for setting a default payment strategy
    public PaymentContext(IPaymentStrategy paymentStrategy)
    {
        _paymentStrategy = paymentStrategy;
    }

    // Method for changing the strategy at runtime
    public void SetPaymentStrategy(IPaymentStrategy paymentStrategy)
    {
        _paymentStrategy = paymentStrategy;
    }

    public void ExecutePayment(decimal amount)
    {
        _paymentStrategy.ProcessPayment(amount);
    }
}

Step 4: Utilizing the Strategy Pattern

Finally, the client code can dynamically choose and change payment methods.

var paymentContext = new PaymentContext(new CreditCardPayment());
paymentContext.ExecutePayment(100.00m);

// Change to PayPal payment dynamically
paymentContext.SetPaymentStrategy(new PayPalPayment());
paymentContext.ExecutePayment(50.00m);

// Change to Cryptocurrency payment dynamically
paymentContext.SetPaymentStrategy(new CryptoPayment());
paymentContext.ExecutePayment(75.00m);

Conclusion

The Strategy Pattern offers a robust framework for managing algorithms, operations, or responsibilities that may vary according to the context in a system. By encapsulating these variations within separate classes and promoting a composition over inheritance strategy, it adheres to fundamental design principles, enhancing maintainability and scalability. In our journey through a payment processing system example, we witnessed firsthand the simplicity and power of applying the Strategy Pattern to solve complex problems elegantly. As software engineers, embracing such patterns not only enriches our design toolkit but also elevates the quality and resilience of our solutions.