Replace Conditional Logic with Strategy Pattern
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.
- Analyzing the sample application
- Creating and running test
- Extracting methods
- Using Extract Delegate refactoring
- Fine tuning
- Implementing the abstract class
- Happy end
Analyzing the sample application
Here's a short example. Let's say, there is a method that calculates insurance costs based on a person's income:
Let's analyze this example. Here we see the four "income bands widths", separated into four calculation strategies. In general, they conform to the following formula:
Our goal is to provide separate classes to calculate each strategy, and transform the original class to make it more transparent.
Creating and running test
First of all, let's make sure that the class works. To do that, create a test class, using the JUnit4 testing framework.
Place the caret at the class name, and then press Alt+Enter (or click ). From the list of suggested intention actions, choose Create Test:
In the Create test dialog, choose JUnit4 from the Testing library drop-down list, click Fix, if you do not have JUnit library, and then click OK:
The stub test class is created. However, you have to provide some meaningful code, using the provided quick fix to create import statement:
Now let's run this test by clicking the run button in the left gutter, or by pressing Ctrl+Shift+F10:
All 4 tests pass:
Next, bring forward the original class, place the caret at the expression
and invoke Extract Method Dialog dialog Ctrl+Alt+M:
You get the following code:
Next, use the same Extract Method refactoring for for 60000, 0.02 and 105600 fragments, and create the methods
Using Extract Delegate refactoring
Next, select all the methods created in the previous chapter, and invoke the Extract delegate example refactoring:
The newly created class has the name
Then delete all the selected methods from the class
IfElseDemo. Thus you get the following two classes:
This code requires some modifications. First, let's change the class
Rename Shift+F6 the field
Make this field not final.
Using the intention action, split it into declaration and initialization:
Move the initialization down to the corresponding
Get the following code:
Next, let's modify the class
InsuranceStrategyVeryHigh - invoke the Extract superclass refactoring for it.
Mind the settings in the Extract Superclass Dialog dialog:
The name of the superclass to be generated is
All the methods of the class
InsuranceStrategyVeryHighare checked - it means that they will be included in the superclass.
calculateInsuranceStrategyVeryHighremains non-abstract; all the other methods are made abstract by selecting the Make Abstract checkboxes.
Agree to replace the usage of
InsuranceStrategyVeryHigh class (in
IfElseDemo class) with the superclass, and get the following
Implementing the abstract class
Next, for this abstract class, use Implement Abstract Class intention to create implementations for all strategies:
Name the new implementation classes
For all new implementations provide correct
return statements for the methods
Thus all implementation classes should look similar to the class
InsuranceStrategyVeryHigh, but with strategy-specific adjustment, weight and constant. For example:
Note that in all newly created implementation classes the class names are grey - they are not used so far.
Next, bring forward the class
IfElseDemo, and modify all the branch bodies so that they initialize the
strategy field, like in the last branch:
Finally, rename the method
calculateInsuranceVeryHigh: bring forward the class
InsuranceStrategy, place the caret at the method name and press Shift+F6. The new name should be
And finally enjoy the code:
Next, let's run the test class again. All tests should pass – we have refactored the code, but it still produces the same results.