Press "Enter" to skip to content

Month: August 2016

Strategy Design Pattern in C#

What it is

The Strategy Design Pattern can be used when you want to perform a function, but you might use different techniques.

For a non-computer example, let’s say I want some food to eat. There are several ways I can accomplish that: cook a meal in my kitchen, go to a restaurant, order food to be delivered to me, etc.

All those strategies perform the “function” I want – “get food”. But they do them differently.

 

Video version of demonstration

 

Examples

For this example, I’ll show a calculator class that needs to calculate the average of a list of numbers, using either the “mean” or “median” technique of averaging. The “strategy” is the different way to compute the average.

 

Non-Pattern Versions

 

Non-pattern version (multiple methods)

This version shows how the calculator class could be written using different functions – one for each averaging technique.

using System.Collections.Generic;
using System.Linq;
namespace Engine.StrategyPattern.NonPatternVersion_MultipleMethods
{
    public class Calculator
    {
        public double CalculateAverageByMean(List<double> values)
        {
            return values.Sum() / values.Count;
        }
        public double CalculateAverageByMedian(List<double> values)
        {
            var sortedValues = values.OrderBy(x => x).ToList();
            if(sortedValues.Count % 2 == 1)
            {
                return sortedValues[(sortedValues.Count - 1) / 2];
            }
            return (sortedValues[(sortedValues.Count / 2) - 1] + 
                sortedValues[sortedValues.Count / 2]) / 2;
        }
    }
}

 

Non-pattern version (single methods)

This is how the class could be written, using a single function with a “switch” statement.

using System;
using System.Collections.Generic;
using System.Linq;
namespace Engine.StrategyPattern.NonPatternVersion_SingleMethod
{
    public class Calculator
    {
        public enum AveragingMethod
        {
            Mean,
            Median
        }
        public double CalculateAverage(List<double> values, AveragingMethod averagingMethod)
        {
            switch(averagingMethod)
            {
                case AveragingMethod.Mean:
                    return values.Sum() / values.Count;
                case AveragingMethod.Median:
                    var sortedValues = values.OrderBy(x => x).ToList();
                    if(sortedValues.Count % 2 == 1)
                    {
                        return sortedValues[(sortedValues.Count - 1) / 2];
                    }
                    return (sortedValues[(sortedValues.Count / 2) - 1] + 
                        sortedValues[sortedValues.Count / 2]) / 2;
                default:
                    throw new ArgumentException("Invalid averagingMethod value");
            }
        }
    }
}

 

Design Pattern Version

 

Strategy Design Pattern version

The version using the design pattern is a little more complex. It uses four classes/interfaces, while the non-pattern versions only use one class. It wouldn’t be worth the extra work, for code as small as this sample.

I would probably only consider using this pattern if there were at least five different ways to perform the strategy’s function. Or, if I could re-use the concrete strategy classes in several places.

 

IAveragingMethod.cs

using System.Collections.Generic;
namespace Engine.StrategyPattern.PatternVersion
{
    public interface IAveragingMethod
    {
        double AverageFor(List<double> values);
    }
}

This is the interface for the strategy.

The interface states that the “concrete strategy” classes (the classes that perform the function) need to have these properties/methods/etc.

For this example, the concrete strategy classes need an “AverageFor” function, which accepts a list of doubles, and returns a double.

 

AverageByMean.cs

using System.Collections.Generic;
using System.Linq;
namespace Engine.StrategyPattern.PatternVersion
{
    public class AverageByMean : IAveragingMethod
    {
        public double AverageFor(List<double> values)
        {
            // Simple method to calculate average: 
            // sum of all values, divided by number of values.
            return values.Sum() / values.Count;
        }
    }
}

The concrete strategy to calculate an average, using the “mean” method.

 

AverageByMedian.cs

using System.Collections.Generic;
using System.Linq;
namespace Engine.StrategyPattern.PatternVersion
{
    public class AverageByMedian : IAveragingMethod
    {
        public double AverageFor(List<double> values)
        {
            // Median average is the middle value of the values in the list.
            var sortedValues = values.OrderBy(x => x).ToList();
            // Use the "%" (modulus) to determine if there is an even, or odd, number of values.
            if(sortedValues.Count % 2 == 1)
            {
                // Number of values is odd.
                // Return the middle value of the sorted list.
                // REMEMBER: The list's index is zero-based, so we subtract 1, instead of adding 1,
                //           to determine which element of the list to return
                return sortedValues[(sortedValues.Count - 1) / 2];
            }
            // Number of values is even.
            // Return the mean average of the two middle values.
            // REMEMBER: The list's index is zero-based, so we subtract 1, instead of adding 1,
            //           to determine which elements of the list to use
            return (sortedValues[(sortedValues.Count / 2) - 1] + 
                sortedValues[sortedValues.Count / 2]) / 2;
        }
    }
}

The concrete strategy to calculate an average, using the “median” method.

 

Calculator.cs

using System.Collections.Generic;
namespace Engine.StrategyPattern.PatternVersion
{
    public class Calculator
    {
        public double CalculateAverageFor(List<double> values, IAveragingMethod averagingMethod)
        {
            return averagingMethod.AverageFor(values);
        }
    }
}

The function to calculate the average receives a list of numbers, and an object that is one of the concrete strategy classes. Because the datatype for the averagingMethod parameter is “IAveragingMethod”, we can pass in any object that implements that interface (matches the requirements for our strategy).

Then, the CalculateAverageFor() method uses the AverageFor() function in the concrete strategy class to perform the calculation.

 

TestCalculator.cs

using System;
using System.Collections.Generic;
using Engine.StrategyPattern.PatternVersion;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace TestEngine.StrategyPattern.PatternVersion
{
    [TestClass]
    public class TestCalculator
    {
        private readonly List<double> _values = new List<double> {10, 5, 7, 15, 13, 12, 8, 7, 4, 2, 9};
        [TestMethod]
        public void Test_AverageByMean()
        {
            Calculator calculator = new Calculator();
            var averageByMean = calculator.CalculateAverageFor(_values, new AverageByMean());
            Assert.IsTrue(ResultsAreCloseEnough(8.3636363, averageByMean));
        }
        [TestMethod]
        public void Test_AverageByMedian()
        {
            Calculator calculator = new Calculator();
            var averageByMedian = calculator.CalculateAverageFor(_values, new AverageByMedian());
            Assert.IsTrue(ResultsAreCloseEnough(8, averageByMedian));
        }
        // Because we are using doubles (floating point values), the values may not exactly match.
        // If the difference between the expected result, and the calculated result is less than .000001,
        // consider the two values as "equal".
        private bool ResultsAreCloseEnough(double expectedResult, double calculatedResult)
        {
            var difference = Math.Abs(expectedResult - calculatedResult);
            return difference < .000001;
        }
    }
}

This is how you would call a method that uses the strategy design pattern.

You pass in the list of numbers to average, and an instance of the appropriate concrete strategy class.

 

Where I’ve found it useful

The most common place for me to use this design pattern is when I need to validate a business object, but have several different ways it might need to be validated. This pattern lets me call a single “Validate” function that accepts the business object and the appropriate concrete strategy validator object.

This design pattern, and the Command Design Pattern, are examples of dependency inversion. Dependency inversion is a technique that can make your code easier to modify, and easier to test.

This is also a technique you can use to implement “composition over inheritance” – another pattern I’ll demonstrate soon.

 

All my design pattern lessons

Source code for my design pattern lessons

 

10 Comments