Click here to Skip to main content
15,882,017 members
Articles / Programming Languages / C#

C# Design Patterns: The Strategy Pattern

Rate me:
Please Sign up or sign in to vote.
2.82/5 (3 votes)
20 Apr 2023CPOL8 min read 12.7K   8   6
Learn how to use the strategy pattern in C# to create flexible and reusable code. Replace if-statements and simplify your code with this design pattern.
Developers love patterns. There are many patterns we can use or follow. A few well-known patterns are the decorator pattern, observer pattern, and builder pattern. There are many more and each has its own pros and cons. This time, I want to show you the strategy pattern in C#. This idea is to easily switch between algorithms without changing the code’s logic. One of the best benefits of using this pattern is wiping away large if-statements.

The Boring Theory

As said in the introduction, the strategy pattern can be used to simply switch between different algorithms without changing the code’s logic. It actually defines a family of algorithms. They are a family because they are connected through an interface, which promises what the implementation can do.

Because of this interface, you can have different classes with the same methods, but different implementations. It’s also pretty easy to create a new implementation without changing any of the existing code’s logic.

So, imagine you have one interface which is used in 4 classes. These 4 classes are identical on the outside (same methods for example), but the methods all have different implementations, making the output different.

The strategy pattern is a pattern to use one of these implementations with a given condition. An interface is key to the strategy pattern.

Alright, enough boring theory. Let’s code!

Article Code

I have created an example console application which I am going to use to show you the strategy pattern. It is advisable to download it if you want to follow this tutorial. You can find the code at:

It contains a solution with two projects; both console applications. Both console applications have the Program.cs and a folder called “Movies”. In that folder are three movies with the same properties and methods.

I will use the StrategyPatternDemo.ConsoleApp first.

Note: Each movie contains the method Age(). In a real-life situation, I would move the Age() method to a base class and use that in the class of a movie. But for example purposes, I leave the method in these classes so you can see how it works.

A Basic Example

The problem with the StrategyPatternDemo.ConsoleApp is that when I want to show information about multiple movies I need to initialize each movie, show all the information as shown now for Shrek, and repeat it until I handled all the movies. This also creates a duplicate code violation.

Another problem is that when another movie is added I need to copy-paste everything from a previously handled movie and make sure it works.

The Program.cs contain a basic code to show the information about the movie “Shrek”. I will continue with this small code base.

Interface

As you can see, there are three movies: Inception, Shrek, and The Matrix. They all have the same properties and the same method. To make use of the strategy pattern, we need an interface. I create an interface called IMovieStrategy and that interface looks like this:

C#
public interface IMovieStrategy
{
    string Title { get; }
    string Description { get; }
    DateTime ReleaseDate { get; }
    int Age();
}

We need to make sure that each movie implements this interface. Now we have created a family of algorithms.

Context

The idea is that we don’t care which part of the family is being shown in the console, or used in the code. This is what the interface is for. But we need some context, literally. This context will have a method that gets a family member and executes the code.

This context will need to receive the strategy it needs to execute and the methods and properties of the strategy (these are known because it’s an interface). I create a new class, called Context.cs, and add the following code:

C#
public class Context
{
    private IMovieStrategy? _movieStrategy;

    public void SetStrategy(IMovieStrategy strategy)
    {
        _movieStrategy = strategy;
    }

    public void ShowMovie()
    {
        if (_movieStrategy == null)
            return;

        Console.WriteLine(_movieStrategy.Title);
        Console.WriteLine(_movieStrategy.Description);

        Console.WriteLine($"{_movieStrategy.ReleaseDate} 
                         ({_movieStrategy.Age()} years old)");
    }
}

The code doesn’t show anything about a specific movie. It’s pretty general. The method SetStrategy receives an implementation of the IMovieStrategy. It doesn’t care which one, only that the class has an implementation of said interface.

As soon as that is set, the method ShowMovie will show all the required information. Because it’s an interface, I only have to type this once and can reuse this for different IMovieStrategy implementations. This also solves the duplicate code violation.

Implementation in the Application

Let’s go back to the Program.cs. We can now initialize the Context class once and then set the strategy per movie. Maybe it’s better if I just show you:

C#
Context context = new();
context.SetStrategy(new Shrek());
context.ShowMovie();
Console.WriteLine("-----------------------------------------------------");

context.SetStrategy(new TheMatrix());
context.ShowMovie();
Console.WriteLine("-----------------------------------------------------");

context.SetStrategy(new Inception());
context.ShowMovie();
Console.WriteLine("-----------------------------------------------------");

First, I initialize the context class. Then I add the movie, which has an implementation of IMovieStrategy. And then I call the ShowMovie() on context to show the movie information.

That’s it! A basic strategy pattern example. I moved all my logic to one class that handles the information (context).

Let’s say I want to change the line that shows the release date and the age. I only have to do that in one place: Context, method ShowMovie(). I don’t need to change it on three different lines, like with the code I had before.

If a fourth movie would be added, the changes in the ShowMovie also apply to that movie.

Replacing If-Statements

But the real reason I use a strategy pattern is to get rid of big if-statements. In the project StrategyPatternPartTwo.ConsoleApp I have created such an if-statement. If you open the Program.cs, you will see the code. Not the best code, but that’s the point.

It doesn’t look like a really big if-statement, nor does the code look complex. But this is a tutorial and not a real-life application. I like to keep the examples small so you know what is happening.

The ‘problems’ in this piece of code: Well, first of all… It’s case-sensitive. If I search for ‘shrek’ (lower s) it will not find the movie. This is the case for all movies and I should fix this in 3 locations. If a 4th movie will be added, I need to make sure I don’t make the same mistake again.

Another problem is growth. There are many movies in the world and maybe I want to add more movies to my application. Adding more and more else-if statements would make the file really big. If I have 100 movies and I discovered I made a mistake in the first one, I need to fix all 100 of them.

I want to remove the if statement completely and let logic ‘decide’ which movie we need. Or rather, which implementation of the interface.

Because just like we did in the previous part, we need an interface. You can just copy-paste the previous interface because it’s the same. Don’t forget to connect the interface to the movies.

A New Context

We are going to inject all the movies we have in a class, called Context. That class will decide, together with the user input, which movie to show. So, let’s create the Context class.

In this Context class, we need to inject the movies. We do this with the interface we created earlier. Since we have multiple movies, we will inject a list of IMovieStrategy.

The method ShowMovie is part of this Context too, but receives the title a user can enter. It’s this title that lets the Context make a decision.

All together, the code looks like this:

C#
public class Context
{
    private readonly IEnumerable<IMovieStrategy> movieStrategies;

    public Context(IEnumerable<IMovieStrategy> movieStrategies)
    {
        this.movieStrategies=movieStrategies;
    }

    public void ShowMovie(string title)
    {
        IMovieStrategy movie = movieStrategies.SingleOrDefault(x => x.Title == title) 
            ?? throw new Exception("Movie not found");

        Console.WriteLine(movie.Title);
        Console.WriteLine(movie.Description);
        Console.WriteLine($"{movie.ReleaseDate} ({movie.Age()} years old)");
    }
}

Notice the line “IMovieStrategy movie = movieStrategies.SingleOrDefault(x => x.Title == title) ?? throw new Exception(“Movie not found”);” What it does is look in the injected IMovieStrategies (multiple) and find the one that contains the title that the user has entered. And this is the whole trick.

If the strategy is found, it can grab that initialized movie and write the information on the screen.

But… How do we get these movies in the injection? For this, we have to move to the Program.cs.

Setting Up Dependencies

We need to configure our injections, just as we normally do. You might want to install the package Microsoft.Extensions.DependencyInjection for this.

To configure dependency injections, we need a ServiceCollection and a ServiceProvider. With the first one, we can configure the strategies and the Context class. After that, we get the service Context and off we go.

C#
ServiceProvider serviceProvider = new ServiceCollection()
    .AddScoped<IMovieStrategy, Shrek>()
    .AddScoped<IMovieStrategy, TheMatrix>()
    .AddScoped<IMovieStrategy, Inception>()
    .AddScoped<Context>()
    .BuildServiceProvider();

Context context = serviceProvider.GetService<Context>();

Console.WriteLine("Type the name of the movie you want to get information about:");
Console.WriteLine("Shrek");
Console.WriteLine("Inception");
Console.WriteLine("The Matrix");
Console.WriteLine("");
Console.Write("Type here: ");
string? name = Console.ReadLine();

context.ShowMovie(name);

As you can see, the if statement is gone and we went from 39 lines of code to 22 (including the usings and service provider). The application still works as it should, but it’s more generic now.

We can fix the case sensitivity easier now. Just go to Context.cs and change the following:

C#
IMovieStrategy movie = movieStrategies.SingleOrDefault(x => x.Title == title)
    ?? throw new Exception("Movie not found");

// To: 

IMovieStrategy movie = movieStrategies.SingleOrDefault
                       (x => x.Title.ToLower() == title.ToLower()) 
    ?? throw new Exception("Movie not found");

A New Strategy

Let’s add the movie “Jaws”. Just simply copy and paste one of the existing movies and rename it to “Jaws”.

C#
public class Jaws: IMovieStrategy
{
    public string Title => "Jaws";
    public string Description => "When a killer shark unleashes chaos 
         on a beach community off Cape Cod, it's up to a local sheriff, 
         a marine biologist, and an old seafarer to hunt the beast down.";
    public DateTime ReleaseDate => new(1975, 12, 18);

    public int Age()
    {
        DateTime currentDate = DateTime.Now;
        int age = currentDate.Year - ReleaseDate.Year;

        if (currentDate < ReleaseDate.AddYears(age))
        {
            age--;
        }

        return age;
    }
}

Then we can add it to our dependency injection configuration and the menu of the console application:

C#
ServiceProvider serviceProvider = new ServiceCollection()
    .AddScoped<IMovieStrategy, Shrek>()
    .AddScoped<IMovieStrategy, TheMatrix>()
    .AddScoped<IMovieStrategy, Inception>()
    .AddScoped<IMovieStrategy, Jaws>()
    .AddScoped<Context>()
    .BuildServiceProvider();

Context context = serviceProvider.GetService<Context>();

Console.WriteLine("Type the name of the movie you want to get information about:");
Console.WriteLine("Shrek");
Console.WriteLine("Inception");
Console.WriteLine("The Matrix");
Console.WriteLine("Jaws");
Console.WriteLine("");
Console.Write("Type here: ");
string? name = Console.ReadLine();

context.ShowMovie(name);

That’s it! Now it works. Just start the application, type “jaws” or “JaWs”, and press enter. You will see the information about Jaws on your screen.

A Step Further

Not really the strategy pattern, but a nice convenience. In the example above, I have typed the names of the movies so the user knows what to choose from. We can also get that information from the strategies.

If we use the strategies, we make it even easier on ourselves, because then we only have to make sure we register the movie with the interface in the service provider. Take a look at the following code:

C#
ServiceProvider serviceProvider = new ServiceCollection()
    .AddScoped<IMovieStrategy, Shrek>()
    .AddScoped<IMovieStrategy, TheMatrix>()
    .AddScoped<IMovieStrategy, Inception>()
    .AddScoped<IMovieStrategy, Jaws>()
    .AddScoped<Context>()
    .BuildServiceProvider();

Context context = serviceProvider.GetService<Context>();
IEnumerable<IMovieStrategy> movies = 
            serviceProvider.GetService<IEnumerable<IMovieStrategy>>();

Console.WriteLine("Type the name of the movie you want to get information about:");

foreach(IMovieStrategy strategy in movies)
{
    Console.WriteLine(strategy.Title);
}

Console.WriteLine("");
Console.Write("Type here: ");
string? name = Console.ReadLine();

context.ShowMovie(name);

Let’s add another movie. Let’s say “The Muppets Take Manhattan”. The code would look like this:

C#
public class TheMuppetsTakeManhattan: IMovieStrategy
{
    public string Title => "The Muppets Take Manhattan";
    public string Description => "Kermit and his friends go to New York City 
         to get their musical on Broadway only to find it's a more difficult task 
         than they anticipated.";
    public DateTime ReleaseDate => new(1984, 12, 20);

    public int Age()
    {
        DateTime currentDate = DateTime.Now;
        int age = currentDate.Year - ReleaseDate.Year;

        if (currentDate < ReleaseDate.AddYears(age))
        {
            age--;
        }

        return age;
    }
}

Next, we add it to the registration of the strategies (the dependency injection):

C#
ServiceProvider serviceProvider = new ServiceCollection()
    .AddScoped<IMovieStrategy, Shrek>()
    .AddScoped<IMovieStrategy, TheMatrix>()
    .AddScoped<IMovieStrategy, Inception>()
    .AddScoped<IMovieStrategy, Jaws>()
    .AddScoped<IMovieStrategy, TheMuppetsTakeManhattan>()
    .AddScoped<Context>()
    .BuildServiceProvider();

And the application shows the new movie:

Conclusion

And there you have it: The strategy pattern in C#. To be honest, it took me a while to get a hold of this pattern. Mostly because I heard about it, but never used it. Most bigger projects I am working on have this pattern, especially when I see that some if-statements are getting too big.

There are a lot of reasons why you can use the strategy pattern. But keep in mind not to go overboard. If the implementation of a strategy is just one line of code or you find yourself having just two strategies: Do yourself a favor and don’t use the pattern.

One thing I didn't talk about was the CanExecute and Execute. When I was finished with this article, I thought this was enough to read. But if you really want more information about this, let me know in the comments.

History

  • 20th April, 2023: Initial version

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior) Kens Learning Curve
Netherlands Netherlands
I am a C# developer for over 20 years. I worked on many different projects, different companies, and different techniques. I was a C# teacher for people diagnosed with ADHD and/or autism. Here I have set up a complete training for them to learn programming with C#, basic cloud actions, and architecture. The goal was to help them to learn developing software with C#. But the mission was to help them find a job suitable to their needs.

Now I am enjoying the freedom of traveling the world. Learning new ways to teach and bring information to people through the internet.

Comments and Discussions

 
Praisegood Pin
Binyu Cui22-Mar-24 17:03
Binyu Cui22-Mar-24 17:03 
Question"As said in the introduction". What introduction? Pin
Paulo Zemek30-Apr-23 15:47
mvaPaulo Zemek30-Apr-23 15:47 
The article starts by saying "As said in the introduction"... but there's no introduction.

GeneralMy vote of 5 Pin
Member 1406674724-Apr-23 19:24
Member 1406674724-Apr-23 19:24 
QuestionSome examples Pin
Dirk_Strauss24-Apr-23 4:19
professionalDirk_Strauss24-Apr-23 4:19 
AnswerRe: Some examples Pin
Kenji Elzerman24-Apr-23 21:52
mvaKenji Elzerman24-Apr-23 21:52 
SuggestionAn Alternative Approach Pin
George Swan20-Apr-23 20:51
mveGeorge Swan20-Apr-23 20:51 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.