Sunday, October 28, 2007 4:22 PM
Recently I came across some code in a framework that was performing different calculations on data as it was retrieved from data source. The framework had a set of derived columns, each of which represented a set of predefined calculations against the retrieved data. For example, we may have had a Sum column which was responsible for adding the values of two other columns together. We also may have had a Difference column which was responsible for subtracting the value of one column from another column. This is a pretty common, straight forward situation which I'm sure that nearly everyone has encountered at some point in their career.
Where this starts to break down is how the different calculations were implemented and specified. Each calculation was represented by a CalculationMode enum which was passed to the Calculate() method along with the necessary parameters. The Calculate() method contained a giant switch statement which checked the value of the CalculationMode enum, and depending on the value it found, performed the necessary calculation. Makes sense, right? Sure it does. It makes sense, it works, and it's easy to implement. To be honest, its probably how I would have implemented it at first, too.
Here's a snapshot of some of our different CalculationModes available...
/// <summary>
/// Specifies the type of calculation to perform.
/// </summary>
public enum CalculationMode
{
. . .
/// <summary>
/// Addition will be performed.
/// </summary>
Addition,
/// <summary>
/// Subtraction will be performed.
/// </summary>
Subtraction,
. . .
}
And here's the method that would use those enums
/// <summary>
/// Performs the calculation specified in mode on the values of op1 and op2 and
/// then returns the results.
/// </summary>
/// <param name="op1">The first operand.</param>
/// <param name="op2">The second operand.</param>
/// <param name="mode">The mode of calculation to perform.</param>
/// <returns>The result of the specified calculation.</returns>
public double Calculate(double op1, double op2, CalculationMode mode)
{
double result;
switch (mode)
{
case CalculationMode.Addition:
result = op1 + op2;
break;
case CalculationMode.Subtraction:
result = op1 - op2;
break;
. . .
default:
throw new Exception("Unknown calculation type encountered.");
}
return result;
}
Finally, this is how we would put this monster to use...
/// <summary>
/// The entry point.
/// </summary>
/// <param name="args">The args.</param>
private static void Main(string[] args)
{
Calculation calculation = new Calculation();
double result = calculation.Calculate(2, 4, CalculationMode.Addition);
Console.WriteLine(result);
}
The problem doesn't become evident until we try to maintain the code and extend it. What happens when we need to add a new calculation mode 6 months from now...say 'Multiplication'. Easy enough, we just go to the Enum method and add a value for Multiplication. Then we just go to the Calculate function and add a case in the giant switch statement to handle Multiplication and implement the algorithm there. Now, recompile. What's that? We want to be able to Divide, too? Well, OK. Let's go back to the switch statement, add a case for 'Division' and then implement the functionality there. We're done! Oops, no we're not. We forgot to add a value back to the enums for Division. Now we're done, right? Nope, we have to recompile the system because we changed a core part of the codebase. OK, so hopefully you've started to see that although this situation worked great to start with...it's definitely not ideal for a long term solution. This is where the Strategy pattern comes in.
The Strategy pattern, according to
Wikipedia, allows algorithms to be selected on-the-fly at runtime. Algorithms on-the-fly at runtime...this sounds like exactly what we need. How do we do this in C#? Easy, we call on our old friend
polymorphism. Polymorphism allows us to specify different implementations of a different contract, or interface in this case, interchangeably. To put it more in our context, we'll simply define an ICalculation interface with a single method Calculate(). This method will allow us to perform whatever calculation our implementor has specified without the consuming object knowing anything about it. This means no monster switch statements, and no recompiling the entire codebase simply to add additional functionality. And we also get the added benefit of abstracting some of our functionality out to interfaces which will help our unit testing out. Here's what our code looks like after we've introduced the strategy pattern.
First, here's the interface that we define to abstract away the actual implementation of the Calculation method...
/// <summary>
/// Abstracts the implementing calculation from the consuming class.
/// </summary>
public interface ICalculation
{
/// <summary>
/// Performs a calulation on the given values as operands.
/// </summary>
/// <param name="op1">The first operand.</param>
/// <param name="op2">The second operand.</param>
/// <returns>The result of the calculation.</returns>
double Calculate(double op1, double op2);
}
And here's a sample implementation of that interface...
/// <summary>
/// Performs addition.
/// </summary>
public class Addition : ICalculation
{
/// <summary>
/// Adds the value of op1 to the value of op2 and returns the results.
/// </summary>
/// <param name="op1">The first operand.</param>
/// <param name="op2">The second operand.</param>
/// <returns>The result of the calculation.</returns>
public double Calculate(double op1, double op2)
{
return (op1 + op2);
}
}
Here's how our new calculate method looks after we've stopped having to worry about the enum and the associated switch statement...
/// <summary>
/// Performs the calculation supplied in calculation on the values of op1 and op2
/// and then returns the results.
/// </summary>
/// <param name="op1">The first operand.</param>
/// <param name="op2">The second operand.</param>
/// <param name="calculation">The implementation of the calculation to perform.</param>
/// <returns>The result of the given calculation.</returns>
public double Calculate2(double op1, double op2, ICalculation calculation)
{
return calculation.Calculate(op1, op2);
}
A lot cleaner, huh? Notice that not only is the switch statement gone, but we no longer have to even worry about what kind of calculation we're dealing with. Whereas the previous implementation of Calculate() had to know not only all of the types of calculations available at compile time, it also had to know what to do with each of them. Our new implementation completely delegates the actual functionality off to the ICalculation implementation which means that it could have entirely new types of calculations added at runtime. This portion of the code can be shipped and will never have to be touched again!
Just to wrap things up, here's how you could use this new implementation...
/// <summary>
/// The entry point.
/// </summary>
/// <param name="args">The args.</param>
private static void Main(string[] args)
{
Calculation calculation = new Calculation();
double result = calculation.Calculate2(2, 4, new Addition());
Console.WriteLine(result);
}
I've include the source code for both implementations in one solution for
download so that you can play around with both ideas. Although enums aren't necessary a code smell for refactoring, they can definitely be a warning sign. Especially if you find yourself having to make changes in more than one place, in this case both the enum and the corresponding switch statement, each time that you want to add new functionality. If this sounds familiar to you, then it may be time to take a look at the strategy pattern for your own needs.