When you have a method with lots of conditional logic (i.e., if statements), you're asking for trouble. Conditional logic is notoriously difficult to manage, and may cause you to create an entire state machine inside a single method. In such a situation, it is better to move each algorithm variation to new classes or to subclasses of the host class. The ideal solution would be to replace these conditional statements with a strategy implementation and delegate the execution to the concrete strategy implementations.
For e.g consider the test code and Calculator implementation given below. The execute methods runs a different algorithm based on the operator chosen.
[TestMethod()]
public void ExecuteMethodShouldCalculateTheSumIfOperationSelectedIsAddition()
{
var calculator = new Calculator();
calculator.SetOperation(Operator.Add);
var result = calculator.Execute(3, 5);
Assert.IsTrue(result == 8);
}
[TestMethod]
public void ExecuteMethodShouldCalculateTheDifferenceIfOperationSelectedIsSubtraction()
{
var calculator = new Calculator();
calculator.SetOperation(Operator.Subtract);
var result = calculator.Execute(3, 5);
Assert.IsTrue(result == -2);
}
[TestMethod]
public void ExecuteMethodShouldCalculateTheMultipledResultIfOperationSelectedIsMultiplication()
{
var calculator = new Calculator();
calculator.SetOperation(Operator.Multiply);
var result = calculator.Execute(3, 5);
Assert.IsTrue(result == 15);
}
[TestMethod]
public void ExecuteMethodShouldCalculateTheDivisionResultIfOperationSelectedIsDivision()
{
var calculator = new Calculator();
calculator.SetOperation(Operator.Divide);
var result = calculator.Execute(15, 5);
Assert.IsTrue(result == 3);
}
public class Calculator
{
private Operator _operator;
public void SetOperation(Operator operation)
{
this._operator = operation;
}
public int Execute(int firstArgument, int secondArgument)
{
if(_operator == Operator.Add)
return firstArgument + secondArgument;
if (_operator == Operator.Subtract)
return firstArgument - secondArgument;
if(_operator == Operator.Multiply)
return firstArgument * secondArgument;
if (_operator == Operator.Divide)
{
if (secondArgument != 0)
return firstArgument / secondArgument;
else
throw new ArgumentException("Divider cannot be zero");
}
return 0;
}
}
public enum Operator
{
Add,
Subtract,
Multiply,
Divide
}
- As a first step to refactoring this code, we use the Extract Method to extract the logic to separate methods as given below.
- Once you have completed all the extractions the code now looks like.
public int Execute(int firstArgument, int secondArgument)
{
if(_operator == Operator.Add)
return PerformAdd(firstArgument, secondArgument);
if (_operator == Operator.Subtract)
return PerformSubtraction(firstArgument, secondArgument);
if(_operator == Operator.Multiply)
return PerformMultiplication(firstArgument, secondArgument);
if (_operator == Operator.Divide)
return PerformDivision(firstArgument, secondArgument);
return 0;
}
private int PerformDivision(int firstArgument, int secondArgument)
{
if (secondArgument != 0)
return firstArgument / secondArgument;
else
throw new ArgumentException("Divider cannot be zero");
}
private int PerformMultiplication(int firstArgument, int secondArgument)
{
return firstArgument * secondArgument;
}
private int PerformSubtraction(int firstArgument, int secondArgument)
{
return firstArgument - secondArgument;
}
private int PerformAdd(int firstArgument, int secondArgument)
{
return firstArgument + secondArgument;
}
- Next we use the ‘Move to another type’ refactoring to move these methods to separate classes, which we’ll make as concrete strategy implementations later.
- After this refactoring your code looks like
public class Calculator
{
private Operator _operator;
public void SetOperation(Operator operation)
{
this._operator = operation;
}
public int Execute(int firstArgument, int secondArgument)
{
if(_operator == Operator.Add)
return AdditionStrategy.PerformAdd(firstArgument, secondArgument);
if (_operator == Operator.Subtract)
return SubtractionStrategy.PerformSubtraction(firstArgument, secondArgument);
if(_operator == Operator.Multiply)
return MultiplicationStrategy.PerformMultiplication(firstArgument, secondArgument);
if (_operator == Operator.Divide)
return DivisionStrategy.PerformDivision(firstArgument, secondArgument);
return 0;
}
}
- Use the ‘Rename’ refactoring to change the name of PerformAdd, PerformSubtraction.. methods to Execute.
- After renaming we’ll use the ‘Extract Superclass’ refactoring to create a superclass for these Strategies.
- Now create an abstract method Execute with the same signature as in the base classes in the newly created superclass.
- Use the resharper intellisense to make the superclass abstract.
- Now move to the subclasses and make the execute method override, using the resharper menu.
- Finally create a new Factory implementation to create concrete instances for these strategies based on the parameter passed and use this in the Calculator class as given below.
public int Execute(int firstArgument, int secondArgument)
{
var operationStrategyFactory = new OperationStrategyFactory(_operator);
var operationStrategy = operationStrategyFactory.Create();
return operationStrategy.Execute(firstArgument, secondArgument);
}
4 comments:
Good sample....Thanks
Thanks Jijo,
Will continue the series with more refactorings.
Nice sample
Thanks Raj
Post a Comment