A Strategy for Using Delegates in C#

Let's look at two ways of implementing a basic strategy pattern for a Calculator class that performs a calculation implemented as a strategy. I'll attempt to make the surface areas of each class the same so we can look at the different requirements needed to implement the pattern through traditional OOP and then by using delegates.

To start, we will be using a common enumerator to define the different methods our Calculator class can perform:

public enum CaluclationType
{
    Add = 0,
    Subtract = 1,
    Multiply = 2,
    Divide = 3
}

When we implement, we want to code that looks like the following where we set the calculation strategy using the previously CaluclationType  enum:

Calculator calc = new Calculator(CaluclationType.Add);
Console.WriteLine(calc.GetValue(1.2, 3.4));

calc.CalculationType = CaluclationType.Subtract;
Console.WriteLine(calc.GetValue(1.2, 3.4));

calc.CalculationType = CaluclationType.Multiply;
Console.WriteLine(calc.GetValue(1.2, 3.4));

calc.CalculationType = CaluclationType.Divide;
Console.WriteLine(calc.GetValue(1.2, 3.4));

Part I. OOP Implementation

To get started with the OOP implementation of our solution we'll need an interface that defines the strategy we are implementing:

public interface ICalculate
{
    double Calculate(double inputA, double inputB);
}

And then use this interface in all the different calculations we'll be needing:

public class Adder : ICalculate
{
    public double Calculate(double inputA, double inputB)
    {
        return inputA + inputB;
    }
}

public class Subtractor : ICalculate
{
    public double Calculate(double inputA, double inputB)
    {
        return inputA - inputB;
    }
}

public class Multiplier : ICalculate
{
    public double Calculate(double inputA, double inputB)
    {
        return inputA * inputB;
    }
}

public class Divider : ICalculate
{
    public double Calculate(double inputA, double inputB)
    {
        return inputA / inputB;
    }
}

Next, we'll define our OopCalculator class that executes the strategy.  The constructor is going to be passed the CaluclationType  enum.  We need to keep member variables for the strategy and the enumerator and we'll have a helper method to instantiate the correct class implementing our ICalculate strategy.

public class OopCalculator
{
    public OopCalculator (CaluclationType type)
    {
        m_calculator = GetCalculation(type);
    }

    private ICalculate m_calculator;
    private CaluclationType m_calculationType;

    public CaluclationType CalculationType
    {
        get { return m_calculationType; }
        set
        {
            m_calculationType = value;
            m_calculator = GetCalculation(value);
        }
    }

    private ICalculate GetCalculation(CaluclationType type)
    {
        switch (type)
        {
            case CaluclationType.Add: return new Adder();
            case CaluclationType.Subtract:return new Subtractor();
            case CaluclationType.Multiply: return new Multiplier();
            case CaluclationType.Divide: return new Divider();
            default: throw new ArgumentException("Unexpected CalculationType");
        }
    }

    public double GetValue(double inputA, double inputB)
    {
        return m_calculator.Calculate(inputA, inputB);
    }
}

Ok, so in the end we are left with one main class, one interface (which could have been an abstract base class as well), four strategy classes and our main class all in approximately 100 lines of code.

Part II. Delegate implementation

In our delegate implementation, we'll be using a delegate in place of the strategy interface so let's first define a really generic delegates as follows:

public delegate TOutput Calculate<TOutput, TInput, TInput2>(TInput input1, TInput2 input2);

And the only other thing we'll be needing is the implementing class.  It will have the same constructor as our OopCalculator and be implemented exactly the same. We'll have a switch statement just like in the OOPCalculator to get the correct strategy.  The main difference is that all the methods containing the calculations will be internal to the class which we'll be wiring up with delegates:

public class Calculator
{
    public Calculator(CaluclationType cType)
    {
        m_calculationType = cType;
        m_calculator = GetCalculation(cType);
    }

    private CaluclationType m_calculationType;
    private Calculate<double, double, double> m_calculator;

    public CaluclationType CalculationType
    {
        get { return m_calculationType; }
        set
        {
            m_calculationType = value;
            m_calculator = GetCalculation(value);
        }
    }

    public double GetValue(double inputA, double inputB)
    {
        return m_calculator(inputA, inputB);
    }

    private Calculate<double, double, double> GetCalculation(CaluclationType value)
    {
        switch (value)
        {
            case CaluclationType.Add: return AddMethod;
            case CaluclationType.Subtract: return SubtractMethod;
            case CaluclationType.Multiply: return MultiplyMethod;
            case CaluclationType.Divide: return DivideMethod;
            default: throw new ArgumentException("Unexpected CalculationType");
        }
    }

    #region Methods

    private static double AddMethod(double input1, double input2)
    {
        return input1 + input2;
    }

    private static double SubtractMethod(double input1, double input2)
    {
        return input1 - input2;
    }

    private static double MultiplyMethod(double input1, double input2)
    {
        return input1 * input2;
    }

    private static double DivideMethod(double input1, double input2)
    {
        return input1 / input2;
    }

    #endregion
}

So in the end using the delegate approach we are left with one main class, one delegate (which can be reused because it's so generic) and around 60 lines of code.  That's significantly less code, classes and resulting complexity to keep track of because we no longer have to have an individual object representing each strategy.

Hopefully this gives you some insight as to the power and usefulness of delegates.

Until next time,

Happy coding


Similar Articles