IntelliJ IDEA 14.1.0 Help

Tutorial. 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

Here's a short example. Let's say, there is a method that calculates insurance costs based on a person's income:

package ifs; class IfElseDemo { public double calculateInsurance() { double income = 15000; if (income <= 10000) { return income*0.365; } else if (income <= 30000) { return (income-10000)*0.2+35600; } else if (income <= 60000) { return (income-30000)*0.1+76500; } else { return (income-60000)*0.02+105600; } } }

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:

(income - adjustment) * weight + constant

Our goal is to provide separate classes to calculate each strategy, and transform the original class to make it more transparent.

Extracting methods

So, the first thing to do is to extract the actual calculations into separate methods. First of all, place the caret at the expression to be extracted, and, for example, choose Refactor | Extract | Method on the main menu, or press Ctrl+Shift+Alt+T, and then choose Extract | Method from the pop-up window, or press Ctrl+Alt+M to invoke the Extract Method refactoring. Since there are two possible options, IntelliJ IDEA will suggest you to choose which expression you want to extract to a method:

ij_ref_extract_method1

Note that highlighting of the selected expression changes according to your choice.

In the following dialog, specify the method name and preview its signature:

ij_ref_extract_method2

Repeat the same refactoring for the other "income bands widths", and finally get the following set of methods:

private double calculateInsuranceVeryHigh(double income) { return (income-60000)*0.02+105600; } private double calculateInsuranceHigh(double income) { return (income-30000)*0.1+76500; } private double calculateInsuranceMedium(double income) { return (income-10000)*0.2+35600; } private double calculateInsuranceLow(double income) { return income*0.365; }

Next, extract calculation of the fixed numeric values into separate methods too. To do that, place the caret at the adjustment, and again extract method (Ctrl+Alt+M), with the name getAdjustment. Repeat the same action with the other values. Note that the usages are immediately replaced:

private int getConstant() { return 105600; } private double getWeight() { return 0.02; } private int getAdjustment() { return 60000; }

Extracting a superclass

Next, extract a superclass with the abstract methods from the IfElseDemo class (for example, choose Refactor | Extract | Superclass on the main menu, or press Ctrl+Shift+Alt+T, and then choose Extract | Superclass from the pop-up window.)

The new superclass will be called InsuranceStrategy, and will contain abstract methods getAdjustment, getWeight and getConstant:

ij_ref_extract_supertclass

After this refactoring, the following abstract class appears:

package ifs; public abstract class InsuranceStrategy { protected double calculateInsuranceVeryHigh(double income) { return (income - getAdjustment()) * getWeight() + getConstant(); } protected abstract int getConstant(); protected abstract double getWeight(); protected abstract int getAdjustment(); }

Modifying abstract class

This class still requires some changes. First, let's rename the calculation method: place the caret at the method name (calculateInsuranceVeryHigh), press Shift+F6 and then change the method name to calculateInsurance.

Next, generate a constructor Alt+Insert, add a field for income, and remove unused parameter income.

The resulting abstract class becomes:

package ifs; public abstract class InsuranceStrategy { private double myIncome; protected InsuranceStrategy(double income) { myIncome = income; } protected double calculateInsurance() { return (myIncome - getAdjustment()) * getWeight() + getConstant(); } protected abstract int getConstant(); protected abstract double getWeight(); protected abstract int getAdjustment(); }

Implementing abstract class

OK... the abstract class is ready, and now it's time to implement it. For this purpose, IntelliJ IDEA provides an intention action. Place the caret at the class name, and click the yellow light bulb (or just press Alt+Enter):

ij_ref_implement_abstract_class

In the Implement Abstract Class dialog box, specify the implementation class name:

ij_ref_implement_abstract_class1

Select methods to implement:

ij_ref_implement_abstract_class2

Repeat this operation three more times to generate the implementation classes InsuranceStrategyLow, InsuranceStrategyMedium, and InsuranceStrategyHigh.

Next, open for editing the class IfElseDemo and change its method CalculateInsurance, so it will use the appropriate strategy for each of income "band widths". On each step, you can use the powerful code completion (Ctrl+Space); so doing, you can type just the capital letters of the camel case class names:

ij_ref_completion

When all the strategies are successfully implemented, there is still "red code" - the method calculateInsurance lacks return statement. With IntelliJ IDEA, it's quite easy to fix: press Alt+Enter, or click the red light bulb, and choose Add Return Statement quick fix:

ij_ref_return

Happy end

And finally enjoy the code:

package ifs; class IfElseDemo extends InsuranceStrategy { public IfElseDemo(double income) { super(income); } public double calculateInsurance(double income) { InsuranceStrategy strategy; if (income <= 10000) { strategy = new InsuranceStrategyLow(); } else if (income <= 30000) { strategy = new InsuranceStrategyMedium(); } else if (income <= 60000) { strategy = new InsuranceStrategyHigh(); } else { return calculateInsurance(income); } return income; }
Last modified: 14 July 2015