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; } }