This repository contains an example that shows how easy is to build a Strategy pattern using dependency injection with .NET
The classes and objects participating in this pattern include:
- Strategy (IStrategy)
Declares a common interface to all concrete implementation must implement.
public interface IStrategy
{
string Name { get; }
string Execute(string message);
}
- ConcreteStrategy (ReverseStrategy, ToLowerStrategy, ToUpperStrategy)
Every concrete strategy implements a single behaviour using the IStrategy interface
public class ToUpperStrategy : IStrategy
{
public string Name => nameof(ToUpperStrategy);
public string Execute(string message)
{
return message.ToUpper();
}
}
- Context (StrategyContext)
Uses the ctor injection to obtain an IEnumerable that contain every IStrategy implementation.
It also has a method to execute a concrete IStrategy implementation.
public class StrategyContext : IStrategyContext
{
private readonly IEnumerable<IStrategy> _strategies;
public StrategyContext(IEnumerable<IStrategy> strategies)
{
_strategies = strategies;
}
public string ExecuteStrategy(
string strategyName,
string message)
{
var instance = _strategies.FirstOrDefault(x =>
x.Name.Equals(strategyName, StringComparison.InvariantCultureIgnoreCase));
return instance is not null ?
instance.Execute(message) :
string.Empty;
}
}
The easiest way to register every strategy behavior in our DI (Dependency Injection) container is to do it like this:
builder.Services.AddTransient<IStrategy, ToUpperStrategy>();
builder.Services.AddTransient<IStrategy, ToLowerStrategy>();
builder.Services.AddTransient<IStrategy, ReverseStrategy>();
However, every time we want to add a new behavior to our Strategy pattern, we must remember to register it.
A better option is to use a bit of Reflection. This way, we can create as many behaviors as we want, and they will always be registered in the DI container.
var strategies = AppDomain.CurrentDomain.GetAssemblies()
.SelectMany(assembly => assembly.GetTypes())
.Where(type => typeof(IStrategy).IsAssignableFrom(type) && !type.IsInterface && !type.IsAbstract);
foreach (var strategy in strategies)
{
builder.Services.AddTransient(
typeof(IStrategy),
strategy);
}
It's really simple.
- Register every Strategy behaviour (either manually or using Reflection).
builder.Services.AddTransient<IStrategy, ToUpperStrategy>();
builder.Services.AddTransient<IStrategy, ToLowerStrategy>();
builder.Services.AddTransient<IStrategy, ReverseStrategy>();
-
In the
StrategyContext
class, the multipleIStrategy
implementations we have registered are resolved into anIEnumerable<IStrategy>
. -
Now, we can filter the collection of
IStrategy
to execute the desired behaviour.
public class StrategyContext : IStrategyContext
{
private readonly IEnumerable<IStrategy> _strategies;
public StrategyContext(IEnumerable<IStrategy> strategies)
{
_strategies = strategies;
}
public string ExecuteStrategy(
string strategyName,
string message)
{
var instance = _strategies.FirstOrDefault(x =>
x.Name.Equals(strategyName, StringComparison.InvariantCultureIgnoreCase));
return instance is not null ?
instance.Execute(message) :
string.Empty;
}
}