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:
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:
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:
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:
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.
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:
IMovieStrategy movie = movieStrategies.SingleOrDefault(x => x.Title == title)
?? throw new Exception("Movie not found");
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
”.
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:
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:
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:
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):
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