Strategy Design Pattern in C#

A strategy is a commonly used behavioral design pattern. We often use it in a code, which based on some input data, has to dynamically choose an algorithm for a given operation. In this article, I will write about the Strategy Design Pattern in C#.

What is a strategy design pattern?

As with an inversion of control, the strategy is based on some input data and executes its logic outside the class in which it is used. We delegate this complex logic to someone else, outside of our class. Our class has its duties and responsibilities, and there is no need to add logic for dynamically selecting an algorithm. It’s not his job to do that. Instead, it will work through the strategy. In this line of thought, the strategy helps our code comply with the SRP principle by separating the unnecessary complex logic for our class elsewhere.

UML Diagram

The strategy is represented by the following UML diagram:

Strategy Design Pattern - UML Diagram
Strategy Design Pattern – UML Diagram

The above diagram, all starts from the interface Strategy, with one having the Boolean method Execute. The interface HAS-A relationship to the context. Depending on that context, a concrete strategy is chosen at runtime, and its execute method is called. The context may be a user input or any configuration in your application.

Example with C#

Let’s assume your application has file upload functionality and provides 3 types of storage โ€“ Database, File System, or Cloud provider. The user can choose files and where to upload these files. This is a typical example where we may need a strategy. Based on the user input, you have to decide where to store the files, and each storage provider requires its algorithm for the job. If using Database you must open a connection to that database if using a file system you have to save it there, and if using a cloud provider, you must save it through an HTTP request, for instance. In our simple console application, we donโ€™t have all this logic, but we have the skeleton of the solution.

Define the Strategy Interface

First of all there is an interface ISavingStrategy, with one method Save:

C#
public interface ISavingStrategy
{
    void Save(Stream file);
}

Define the specific strategies

Each saving strategy implements ISavingStrategy interface:

C#
public class DatabaseSavingStrategy : ISavingStrategy
{
    public void Save(Stream file)
    {
        Console.WriteLine($"Database storage chosen chosen!");
    }
}

public class FileSystemSavingStrategy : ISavingStrategy
{
    public void Save(Stream file)
    {
        Console.WriteLine($"File system storage chosen chosen!");
    }
}

public class CloudSavingStrategy : ISavingStrategy
{
    public void Save(Stream file)
    {
        Console.WriteLine($"Cloud storage chosen chosen!");
    }
}

Use the strategies

With the strategies defined, here is how you can use them:

C#
static void Main(string[] args)
{
    var serviceProvider = new ServiceCollection()
        .AddSingleton<IFileService, FileService>()
        .BuildServiceProvider();

    Console.WriteLine("Where do you want to store your files?");
    Console.WriteLine(new string('=', 40));
    Console.WriteLine("Database (D), File system (F), Cloud (C)");
    var storage = Console.ReadLine().ToLower();

    ISavingStrategy savingStrategy = null;
    switch (storage)
    {
        case "d":
            savingStrategy = new DatabaseSavingStrategy();
            break;
        case "f":
            savingStrategy = new FileSystemSavingStrategy();
            break;
        case "c":
            savingStrategy = new CloudSavingStrategy();
            break;
    }

    if (savingStrategy != null)
    {
        var fileService = serviceProvider.GetService<IFileService>();
        using (var stream = new MemoryStream())
        {
            fileService.Save(stream, savingStrategy);
        }
    }
    else
    {
        throw new ArgumentException("Invalid saving provider!");
    }
}

Here we are asking the user to choose a storage provider and then based on that choice we select the appropriate strategy and pass it to the FileService method Save. The method itself calls the specific strategy save method:

C#
public class FileService : IFileService
{
    public void Save(Stream file, ISavingStrategy strategy)
    {
        strategy.Save(file);
    }
}

Resources