Design Pattern: Strategy design pattern
Reference
Motivation
-
Aims to extract algorithm (a strategy) from the host class that needs it (the context or consumer). That allows the consumer to decide on the strategy (algorithm) to use at runtime.
-
Advantage: this pattern abstracts the implementation of the logic from both
Context
class and its consumers. This allows us to change the strategy or implementation during either object creation or at runtime without the instance object knowing.
Participants
-
Context
- Method:
+ SomeOperation(): void
- Holds a reference to an
IStrategy
and delegates the algorithm execution to it.
- Method:
-
IStrategy (Interface)
- Method:
+ ExecuteAlgo(): void
- Declares a method that all supported strategies must implement.
- Method:
-
ConcreteStrategy1
- Inherits:
IStrategy
- Implements:
+ ExecuteAlgo(): void
- Inherits:
-
ConcreteStrategy2
- Inherits:
IStrategy
- Implements:
+ ExecuteAlgo(): void
- Inherits:
Example Implementation
Here is a simple example implementation of the Strategy Pattern. The goal is to demonstrate and understand how the Strategy Design Pattern helps simplify and organize logic during runtime. In this example, we implement sorting logic. Depending on the scenario, we need to dynamically switch the sorting logic to either ascending or descending order.
ISortStrategy
namespace MySortingMachine;
public interface ISortStrategy
{
IOrderedEnumerable<string> Sort(IEnumerable<string> input);
}
SortableCollection
using System.Collections.Immutable;
namespace MySortingMachine;
public sealed class SortableCollection
{
private ISortStrategy _sortStrategy;
private ImmutableArray<string> _items;
public IEnumerable<string> Items => _items;
public SortableCollection(IEnumerable<string> items)
{
_items = items.ToImmutableArray();
_sortStrategy = new SortAscendingStrategy();
}
public void SetSortStrategy(ISortStrategy strategy)
=> _sortStrategy = strategy;
public void Sort()
{
_items = _sortStrategy
.Sort(Items)
.ToImmutableArray()
;
}
}
SortAscendingStrategy
namespace MySortingMachine;
public class SortAscendingStrategy : ISortStrategy
{
public IOrderedEnumerable<string> Sort(IEnumerable<string> input)
=> input.OrderBy(x => x);
}
SortDescendingStrategy
namespace MySortingMachine;
public class SortDescendingStrategy : ISortStrategy
{
public IOrderedEnumerable<string> Sort(IEnumerable<string> input)
=> input.OrderByDescending(x => x);
}
public class SortDescendingStrategyClassic : ISortStrategy
{
public IOrderedEnumerable<string> Sort(IEnumerable<string> input)
{
return input.OrderByDescending(x => x);
}
}
Consumer Code
using MySortingMachine;
using System.Text.Json;
using System.Text.Json.Serialization;
SortableCollection data = new(new[] { "Lorem", "ipsum", "dolor", "sit", "amet." });
var builder = WebApplication.CreateBuilder(args);
builder.Services.ConfigureHttpJsonOptions(options => {
options.SerializerOptions.Converters.Add(new JsonStringEnumConverter());
});
var app = builder.Build();
app.MapGet("/", () => data);
app.MapPut("/", (ReplaceSortStrategy sortStrategy) =>
{
ISortStrategy strategy = sortStrategy.SortOrder == SortOrder.Ascending
? new SortAscendingStrategy()
: new SortDescendingStrategy();
data.SetSortStrategy(strategy);
data.Sort();
return data;
});
app.Run();
public enum SortOrder
{
Ascending,
Descending
}
public record class ReplaceSortStrategy(SortOrder SortOrder);
Conclusion
- Strategy pattern is very effective to delegate the responsiblity to other objects.
- Strategy pattern allows us to have a rich interface (context) with behaviors that can change at a runtime.