How to Implement the Strategy Pattern in C# for Improved Code Flexibility

The Strategy Pattern is a behavioral design pattern that allows you to define a family of algorithms, encapsulate each one, and make them interchangeable within a codebase. The Strategy Pattern allows us to easily switch between different algorithms or strategies at runtime, resulting in improved code flexibility. In this article, I’ll show how to implement the Strategy Pattern in C# along with information about the pattern in general

Let’s jump right into it!


Understanding the Components of the Strategy Pattern

The three components of the Strategy pattern are the Context, Strategy, and Concrete Strategy. The Context class is responsible for receiving requests and assigning them to a particular strategy. The Strategy class defines the interface for each variation of the algorithm and the Concrete Strategy class implements the algorithm in question. These components work together to allow for flexibility in code.

Context Class

The Context class is responsible for keeping track of the current strategy and delegating requests to the correct strategy object. The Context class interfaces with clients and hides the implementation details of the strategies. It’s important in the Strategy Pattern because it allows for multiple algorithms to be interchangeable easily without affecting the client’s code.

Strategy and Concrete Strategy Classes

The Strategy class defines a common interface for all concrete strategies. Each Concrete Strategy Class implements the operations defined by the Strategy class. The Strategy class allows for multiple algorithm implementations without modification of context. This Object-Oriented Programming (OOP) principle, named Liskov Substitution Principle (LSP), is important for creating maintainable and extensible software.

As an example, consider a photo editing application. The Context class could include various image filters to apply to the photos (e.g., contrast, brightness, and saturation). The Context class would handle the user’s requests for the filters but utilize the appropriate Concrete Strategy Class to execute the request.

The OOP principle of inheritance could be used in designing the Concrete Strategy classes. A parent class would outline the implementation for a general filter, whereas subclasses would specify details unique for specific filters. The filter subclasses would only need to worry about implementing the filter-specific details, making maintenance and extension simpler.


Implementing the Strategy Pattern in C#

To implement the Strategy Pattern in a C# codebase, we need to define the three components of the pattern – the Context, Strategy, and Concrete Strategy classes. Here’s some example code to demonstrate these classes in action:

 public interface IStrategy
{
    void Execute();
}

public class ConcreteStrategyA : IStrategy
{
    public void Execute()
    {
        Console.WriteLine("Executing Concrete Strategy A");
    }
}

public class ConcreteStrategyB : IStrategy
{
    public void Execute()
    {
        Console.WriteLine("Executing Concrete Strategy B");
    }
}

public class Context
{
    private readonly IStrategy strategy;

    public Context(IStrategy strategy)
    {
        this.strategy = strategy;
    }

    public void ExecuteStrategy()
    {
        this.strategy.Execute();
    }
}

static void Main(string[] args)
{
    var context = new Context(new ConcreteStrategyA());
    context.ExecuteStrategy();

    context = new Context(new ConcreteStrategyB());
    context.ExecuteStrategy();
}

This code defines the three components of the Strategy Pattern, with IStrategy being the Strategy interface, ConcreteStrategyA and ConcreteStrategyB being the implementations of that interface, and Context being the class that holds a reference to the particular IStrategy concrete implementation.

A key benefit of using the Strategy Pattern is that it provides flexibility in our codebase. By defining the interface and concrete implementation classes separately, we can change the behavior of our application by injecting different concrete implementations of our interface into our Context class. This may allow for our code more modular and extensible.

However, there are pitfalls to avoid when implementing the Strategy Pattern. One important one is to ensure that your concrete classes are designed in a way that follows Object-Oriented Programming principles, particularly the Single Responsibility Principle. Keeping these principles in mind will make our codebase more maintainable and easier to extend.


E-Commerce Example of the Strategy Pattern in C#

In this section, I’ll frame up how the Strategy Pattern can be implemented in a real-world example. Let’s consider a scenario where we’re building an e-commerce website, and we have a promotion system that offers different discounts to customers based on their membership tier. We can use the Strategy Pattern to simplify the code and make it more flexible.

Code Example of the Strategy Pattern in C#

We can start by creating a Context class that will manage the promotion system and apply the discount based on the user’s membership tier. The Context class will have a reference to the Strategy class that will perform the discounts. In this example, the Strategy class could be named MembershipTierDiscountStrategy, which takes the user’s membership tier as input and applies the appropriate discount.

First, define an interface for the discount strategy:

public interface IDiscountStrategy
{
    decimal ApplyDiscount(decimal originalPrice);
}

Then, create concrete strategies for different membership tiers:

public class BronzeMembershipDiscount : IDiscountStrategy
{
    public decimal ApplyDiscount(decimal originalPrice)
    {
        return originalPrice * 0.90M; // 10% discount for bronze members
    }
}

public class SilverMembershipDiscount : IDiscountStrategy
{
    public decimal ApplyDiscount(decimal originalPrice)
    {
        return originalPrice * 0.85M; // 15% discount for silver members
    }
}

public class GoldMembershipDiscount : IDiscountStrategy
{
    public decimal ApplyDiscount(decimal originalPrice)
    {
        return originalPrice * 0.80M; // 20% discount for gold members
    }
}

Next, implement the Context class:

public class DiscountContext
{
    private IDiscountStrategy _discountStrategy;

    public DiscountContext(IDiscountStrategy discountStrategy)
    {
        _discountStrategy = discountStrategy;
    }

    public void SetDiscountStrategy(IDiscountStrategy discountStrategy)
    {
        _discountStrategy = discountStrategy;
    }

    public decimal ApplyDiscount(decimal originalPrice)
    {
        return _discountStrategy.ApplyDiscount(originalPrice);
    }
}

Finally, demonstrate how the pattern works in practice:

public class Program
{
    public static void Main()
    {
        var originalPrice = 100M;

        var bronzeDiscount = new BronzeMembershipDiscount();
        var silverDiscount = new SilverMembershipDiscount();
        var goldDiscount = new GoldMembershipDiscount();

        var discountContext = new DiscountContext(bronzeDiscount);
        Console.WriteLine("Bronze Discount: " + discountContext.ApplyDiscount(originalPrice));

        discountContext.SetDiscountStrategy(silverDiscount);
        Console.WriteLine("Silver Discount: " + discountContext.ApplyDiscount(originalPrice));

        discountContext.SetDiscountStrategy(goldDiscount);
        Console.WriteLine("Gold Discount: " + discountContext.ApplyDiscount(originalPrice));
    }
}

This code example demonstrates how the Strategy Pattern in C# allows for flexible and maintainable discount strategies for different membership tiers in an e-commerce website. It simplifies the process of applying discounts and makes it easy to introduce new discount strategies without altering existing code.

Benefits of the Strategy Pattern for the E-Commerce Site

By using the Strategy Pattern, we can easily switch between different discount strategies, such as seasonal discounts or flash sales, without modifying the Context class. We can also add new discount strategies without affecting the existing code. This provides a lot of flexibility in the codebase, making it easier to maintain and extend in the future.

Another benefit of using the Strategy Pattern in this example is that it simplifies the code. Instead of having a switch statement to check the user’s membership tier and apply the appropriate discount, we can encapsulate that behavior in the MembershipTierDiscountStrategy class. This makes the code easier to read and maintain.

Finally, using the Strategy Pattern allows the code to be easily extended. For example, we could add a LoyaltyPointsDiscountStrategy to apply discounts based on the user’s loyalty points balance. We can add this functionality without modifying the existing code and without affecting the existing discount strategies.


Wrapping Up The Strategy Pattern in C#

In this article, I covered how to implement the Strategy Pattern in C# for improved code flexibility. We began by understanding the Strategy Pattern’s three components, namely the Context, Strategy, and Concrete Strategy. Then, we jumped into the advantages of the Strategy Pattern and how to implement it in C# code with a really generic example. After we spiced things up with a more realistic example of how this could be implemented.

The Strategy Pattern provides code flexibility that allows us to write code that is not only maintainable but also extendable. By implementing this pattern, software engineers can create code that is robust and streamlined. Although the implementation can be tricky, the benefits outweigh the costs.

If you’re interested in more learning opportunities, subscribe to my free weekly newsletter and check out my YouTube channel!

Affiliations:

These are products & services that I trust, use, and love. I get a kickback if you decide to use my links. There’s no pressure, but I only promote things that I like to use!

      • RackNerd: Cheap VPS hosting options that I love for low-resource usage!
      • Contabo: Alternative VPS hosting options with very affordable prices!
      • ConvertKit: This is the platform that I use for my newsletter!
      • SparkLoop: This service helps me add different value to my newsletter!
      • Opus Clip: This is what I use for help creating my short-form videos!
      • Newegg: For all sorts of computer components!
      • Bulk Supplements: For an enormous selection of health supplements!
      • Quora: I try to answer questions on Quora when folks request them of me!

    author avatar
    Nick Cosentino Principal Software Engineering Manager
    Principal Software Engineering Manager at Microsoft. Views are my own.

    This Post Has 2 Comments

    1. Vishnupriya Suresh

      What is the need or benefit of having the context class in this example? What is the problem will call the concrete discount classes’s Apply discount method directly from Main?

      1. Nick Cosentino

        Hey there! These are very simple examples to demonstrate the point of the pattern, so please don’t interpret things as being “problematic” to go in other directions.

        The idea is that the context class is defining the API that you would like to have used from your calling locations. We are able to switch out the strategy used inside of the context to result in different behavior but never need to modify the API of the context class itself. The little demo program simply just shows that we can make the exact same API calls and because the strategy is changed under the hood, we get different results.

        The program as it is does not offer value aside from attempting to demonstrate this – and in fact, it’s so simple right now that yes, you could likely just write a couple of if-statements to reduce the complexity. However, it was to show a consistent API with different behavior.

        I hope that helps clarify!

    Leave a Reply