In the previous post, I mentioned Mario Fusco who wrote a blog post series questioning the way Java developers are implementing the Gang of Four (GoF) patterns.
I am trying to provide a C# version of Mario’s recommendations expanding some examples. In this post, I would like to talk about the Strategy Pattern.
The Strategy Pattern
The goal of this pattern is to define a family of algorithms, encapsulate each one, and make them interchangeable. The algorithm varies independently from clients that use it.
There are three primary participants in this pattern:
- The strategy interface, which declares an interface common to all supported algorithms.
- The strategy concrete implementation, which implements the algorithm according to the strategy interface.
- The execution context, which is configured with a specific implementation and to define an interface that lets the strategy access its data.
How it is commonly adopted today
The first step is putting an abstract definition of the behaviors (algorithms) we want to support into an interface. Let’s assume we need to provide different sorting algorithms. So, the first step would be to define a standard interface with a sort method:
public interface IArraySortStrategy
{
T[] Sort<T>(T[] input, Comparison<T> comparison);
}
Then, we would need to write concrete implementations of the sorting Strategy:
public class QuickSortStrategy : IArraySortStrategy
{
public T[] Sort<T>(T[] input, Comparison<T> comparison)
{
// ..
}
}
public class MergeSortStrategy : IArraySortStrategy
{
public T[] Sort<T>(T[] input, Comparison<T> comparison)
{
// ..
}
}
public class BubbleSortStrategy : IArraySortStrategy
{
public T[] Sort<T>(T[] input, Comparison<T> comparison)
{
// ..
}
}
After that, we would be ready to use it.
namespace HelloStrategy.Controllers
{
[Route("api/[controller]")]
public class ValuesController : Controller
{
private readonly IArraySortStrategy _sortingStrategy;
public ValuesController(IArraySortStrategy sortingStrategy)
{
_sortingStrategy = sortingStrategy;
}
// ..
}
}
Mario’s recommendation
The strategy implementation using interfaces is too verbose. Don’t you think? We are defining classes just to wrap functions.
Let’s use a delegate instead of an interface:
public delegate T[] SortingAlgorithm<T>(T[] input, Comparison<T> comparison);
Now, we can write concrete implementations as simple functions:
public static class SortingImplementations
{
public static T[] QuickSort<T>(T[] input, Comparison<T> comparison) { /* .. */ }
public static T[] MergeSort<T>(T[] input, Comparison<T> comparison) { /* .. */ }
public static T[] BubbleSort<T>(T[] input, Comparison<T> comparison) { /* .. */ }
}
This is an apparently less coupled solution. We do not have to reference the strategy interface in the concrete implementation. So, we could provide concrete implementations in different assemblies with no dependencies.
In the context, we just need to specify the delegate we need instead of the interface.
namespace HelloStrategy.Controllers
{
[Route("api/[controller]")]
public class ValuesController : Controller
{
private readonly SortingAlgorithm<Customer> _sortingAlgorithm;
public ValuesController(SortingAlgorithm<Customer> sortingAlgorithm)
{
_sortingAlgorithm = sortingAlgorithm;
}
// ..
}
}
The dependency injection container provided by ASP.net core supports it:
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton>(SortingImplementations.QuickSort);
// Add framework services.
services.AddMvc();
}
That’s it. Next time, we will talk about the Template pattern.