The strategy pattern - Advanced (Resolver Pattern)

The Strategy Pattern

This pattern is a simpler design pattern - it allows you to:

  1. Encapsulate a family of related algorithms
  2. Allow them to vary
  3. Allow a class to maintain a single purpose and implementation
  4. Seperate the calculation from the delivery of its results

By doing this we can maintain standard principles, leaving the code open to extension but closed to modification.

Simple Example

/// <summary>
    /// The Strategy abstract class, which defines an interface common to all supported strategy algorithms.
    /// </summary>
    abstract class CookStrategy
    {
        public abstract void Cook(string food);
    }

    /// <summary>
    /// A Concrete Strategy class
    /// </summary>
    class Grilling : CookStrategy
    {
        public override void Cook(string food)
        {
            Console.WriteLine("\nCooking " + food + " by grilling it.");
        }
    }

    /// <summary>
    /// A Concrete Strategy class
    /// </summary>
    class OvenBaking : CookStrategy
    {
        public override void Cook(string food)
        {
            Console.WriteLine("\nCooking " + food + " by oven baking it.");
        }
    }

    /// <summary>
    /// A Concrete Strategy class
    /// </summary>
    class DeepFrying : CookStrategy
    {
        public override void Cook(string food)
        {
            Console.WriteLine("\nCooking " + food + " by deep frying it");
        }
    }

    /// <summary>
    /// The Context class, which maintains a reference to the chosen Strategy.
    /// </summary>
    class CookingMethod
    {
        private string Food;
        private CookStrategy _cookStrategy;

        public void SetCookStrategy(CookStrategy cookStrategy)
        {
            this._cookStrategy = cookStrategy;
        }

        public void SetFood(string name)
        {
            Food = name;
        }

        public void Cook()
        {
            _cookStrategy.Cook(Food);
            Console.WriteLine();
        }
    }

Simple Execution


CookingMethod cookMethod = new CookingMethod();

Console.WriteLine("What food would you like to cook?");
var food = Console.ReadLine();
cookMethod.SetFood(food);

Console.WriteLine("What cooking strategy would you like to use (1-3)?");
int input = int.Parse(Console.ReadKey().KeyChar.ToString());

switch(input)
{
    case 1:
        cookMethod.SetCookStrategy(new Grilling());
        cookMethod.Cook();
        break;

    case 2:
        cookMethod.SetCookStrategy(new OvenBaking());
        cookMethod.Cook();
        break;

    case 3:
        cookMethod.SetCookStrategy(new DeepFrying());
        cookMethod.Cook();
        break;

    default:
        Console.WriteLine("Invalid Selection!");
        break;
}
Console.ReadKey();

The limmitation

For this and almost every single example I can find in existance its basically a way to register your types and apply them.
The switch is essentially moved but calling through and doing what you need to do is still in the wrong place and you need to know exaclty the method you want to use!! We need something more dynamic, where the stratergy knows what it is applied to and based on the information provided by the food in this instance, would be chosen for the execution of that dish!

The Resolver Pattern

This is where the seperation comes in, as I cannot find an example that follows this I will coin it was the resolver pattern.
Each ‘Resolver’ is registered with the application for dependancy injection, like anything else and its very existance now allows it to be used and identifed by the data processing it.

Lets visit the food example again to see how this would be done in practice

We start with a service, its job? Using all available ‘resolvers’ it will process the data it is given (Food dishes)

public interface IDishResolutionService
{
    Task ResolveDishesAsync(
        IEnumerable<FoodDish> recordData);
}

Its implementation will have one job, identify the required / available resolver and execute it.
The resolvers themselves have thier own interface to work from, one method to execute, one to identify what meal type it applies to

public interface IFoodCookingResolver
{
    bool AppliesTo(string control);

    Task CookFoodAsync(IDbConnection conn, FoodDish dish);
}

The service implementaion will take all available resolvers via DI

public DishResolutionService(
    IConnectionFactory databaseConnectionFactory,
    IEnumerable<IFoodCookingResolver> dishResolvers)
{
    _satabaseConnectionFactory = databaseConnectionFactory ??
        throw new ArgumentNullException(nameof(databaseConnectionFactory));

    _dishResolvers = dishResolvers ??
        Enumerable.Empty<IFoodCookingResolver>();
}

These are registered with anything else, heres the fancy way in an MVC app:

public void ConfigureServices(IServiceCollection services)
{
    services.TryAddScoped<IDishResolutionService>(ctx =>
    {
        return new DishResolutionService(ctx.GetService<IConnectionFactory>(), GetResolvers(ctx));
    });
}

private IEnumerable<IFoodCookingResolver> GetResolvers(IServiceProvider ctx)
{
    yield return new FishResolver(ctx.GetService<IConnectionFactory>());
    yield return new SingleMealResolver(ctx.GetService<IConnectionFactory>());
    yield return new FamilyBBQResolver(ctx.GetService<IConnectionFactory>());
    yield return new DefaultResolver();
}

Each new resolver will simply be registered in the project and this will allow it to be picked up in the service that executes the food cooking based on data within the food item itself, leaving the original code untouched and including a default resolver to go to if none apply to it specifically.

Example Resolver

public class FishResolver : IFoodCookingResolver
{
    private readonly IDbConnection _dbConnection;

    public FishResolver(IDbConnection conn)
    {
        _dbConnection = conn ??
            throw new ArgumentNullException(nameof(conn));
    }

    public bool AppliesTo(string control) => control == FoodType.OvernFood.UppercaseName();

    public async Task CookFoodAsync(FoodDish meal)
    {
        await _dbConnection.QueryAsync("select 'example, im not doing any data stuff here'");

        console.writeline($"Cooking that in the oven - as I oven cooks things like {meal.Name}");
    }
}

As with this example, all resolvers state what they apply to, so when the service seeks the required resolver, it identifies itself and has contained logic for what to do with this type of food.

public async Task ResolveControlsAsync(
            IEnumerable<RecordData> recordData)
    {
        var groups = recordData
            .SelectMany(x => x.Controls, (x, meta) => (x.Ruid, meta))
            .GroupBy(x => x.meta.ControlType);

        var tasks = new List<Task>();
        using (var conn = _databaseConnectionFactory.GetConnection(DatabaseType.HR))
        {
            foreach (var item in groups)
            {
                tasks.Add(
                    _controlDataResolvers
                    .FirstOr(x => x.AppliesTo(item.Key), () => new DefaultResolver())
                    .CookFoodAsync(conn, item.ToList())
                );
            }

            var results = await Task.WhenAll(tasks);
            return results.SelectMany(x => x);
        }
    }

In the example above the service groups by the type of meal and sends them all off to be handled in the resolver togther - one at a time or as a group is fine depending on what your doing.
So the service checks the available resolvers, picks the one for the data it has, executes the resolver against the object and completes.
The core setup need no longer be changed, but the resolvers can be added to, registered and the system keeps working as it did, default resolvers are just a backup here, where say the oven is used by default unless a specific resolver says otherwise.

This allows the resolvers to differ 100% in what they do, what services they use etc. Keeping the flow of logic but seperation of type.

Function Programming in C#

Functional Programming

Functional programming is a style that treats computation as the evaluation of
mathematical functions and avoids changing-state and mutable data.

Immutable Types

This is an object whos state cannot be modified after it is created.

Mutable

public class Rectangle
{
     public int Length {get;set;}
     public int Height {get;set;}

     public void Grow(int length, int height)
     {
     Length += length;
     Height += height;
     }
}
Rectangle r = new Rectangle();
r.Length = 5;
r.Height = 10;
r.Grow(10, 10);
// r.Length is 15, r.Height is 20, same
instance of r

Immutable

public class ImmutableRectangle
{
     int Length { get; }
     int Height { get; }

     public ImmutableRectangle(int length,
     int height)
     {
         Length = length;
         Height = height;
     }
     public ImmutableRectangle Grow(int length, int height) => new ImmutableRectangle(Length + length, Height + height);
}
ImmutableRectangle r = new
ImmutableRectangle(5, 10);

r = r.Grow(10, 10);
// r.Length is 15, r.Height is 20, is a new
instance of r

Expressions vs Statements

Statments define an actiona nd are executed for thier side-effect.
Expressions produce a result without mutating state.

Statement
public static string GetSalutation(int hour) {
 string salutation; // placeholder value
 if (hour < 12)
 salutation = "Good Morning";
 else
 salutation = "Good Afternoon";
 return salutation; // return mutated variable
}
Expression
public static string GetSalutation(int hour) =>
 hour < 12 ? "Good Morning" : "Good Afternoon";

Value Tuples

Tuple is a more efficient and more productive lightweight syntax to define a data structure that carries more than one value.
Requires NuGet Package System.ValueTuple

  • Represent data without DTO classes
  • Lower memory footprint than a class
  • Return multiple values from methods without the need for out variables
(double lat, double lng) GetCoordinates(string
query)
{
    //DO search query ...
    return (lat: 47.6450905056185,
    lng: 122.130835641356);
}
var pos = GetCoordinates("15700 NE 39th St,
Redmond, WA");
pos.lat; //47.6450905056185
pos.lng; //122.130835641356

Func Delegates

Func Delegates encapsulate a method. When declaring a Func, input and output parameters are specified as T1-T16, and TResult.

  • Func – matches a method that takes no arguments, and returns value of type TResult.
  • Func<T, TResult> – matches a method that takes an argument of type T, and returns value of type TResult.
  • Func<T1, T2, TResult> – matches a method that takes arguments of type T1 and T2, and returns value of type TResult.
  • Func<T1, T2, …, TResult> – and so on up to 16 arguments, and returns value of type TResult.
Func<int, int> addOne = n => n +1;
Func<int, int, int> addNums = (x,y) => x + y;
Func<int, bool> isZero = n => n == 0;

Console.WriteLine(addOne(5)); // 6
Console.WriteLine(isZero(addNums(-5,5))); //
True

int[] a = {0,1,0,3,4,0};
Console.WriteLine(a.Count(isZero)); // 3

Higher Order Functions / Functions as Data

Higher-order function is a function taking one or more function parameters as input, or returning a function as output. The other functions are called first-order functions. (Again, in C#, the term function and the term method are identical.) C# supports higher-order function from the beginning, since a C# function can use almost anything as its input/output, except:

Static types, like System.Convert, System.Math, etc., because there cannot be a value (instance) of a static type.
Special types in .NET framework, like System.Void.
A first-order function can take some data value as input and output:

method signature
int IEnumerable.Count<T>(Func<T, Bool>
predicate)
Source code for Count()
int count = 0;
 foreach (TSource element in source)
 {
 checked // overflow exception check
 {
 if (predicate(element)) //
func<T,Bool> invoked
 {
 count++;
 }
 }
 }
return count;
usage
bool[] bools = { false, true, false, false };

int f = bools.Count(bln => bln == false); //
out = 3
int t = bools.Count(bln => bln == true); // out
= 1

Method Chaining (~Pipelines)

Since C# lacks a Pipeline syntax, pipelines in C# are created with design patterns that allow for methods to chain.
The result of the method chain should produce the desired value and type.

string str = new StringBuilder()
 .Append("Hello ")
 .Append("World ")
 .ToString()
 .TrimEnd()
 .ToUpper();

Extension Methods

Extension methods are a great way to extend method chains and add functionality to a class.

// Extends the StringBuilder class to accept a predicate
public static StringBuilder AppendWhen( this StringBuilder sb, string value, bool predicate) =>
 predicate ? sb.Append(value) : sb;

string htmlButton = new StringBuilder()
 .Append("<button")
 .AppendWhen(" disabled", isDisabled)
 .Append(">Click me</button>")
 .ToString();

Yield

Using yield to define an iterator removes the need for an explicit extra class (the class that holds the state for an enumeration.
You consume an iterator method by using a foreach statement or LINQ query.
Yield is the basis for many LINQ methods.

// Without Yield
public static IEnumerable<int>
GreaterThan(int[] arr, int gt) {
 List<int> temp = new List<int>();
 foreach (int n in arr) {
 if (n > gt) temp.Add(n);
}
 return temp;
}
// With Yield
public static IEnumerable<int>
GreaterThan(int[] arr, int gt) {
 foreach (int n in arr) {
 if (n > gt) yield return n;
 }
}

LINQ

The gateway to functional programming in C#. LINQ makes short work of most imperative programming routines that work on arrays and collections

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×