Refactoring Nested If‑Else Statements with Design Patterns in Java
This article examines the problems of deeply nested if‑else statements in Java code and demonstrates how to replace them with cleaner alternatives such as the Factory pattern, enums, the Command pattern, and a simple rule engine, improving readability and maintainability.
While refactoring legacy code, the author encountered a method containing thirty levels of nested if‑else statements, illustrating how such structures quickly become hard to read and maintain.
Overview – if‑else is a fundamental construct in any programming language, but excessive nesting increases complexity and hampers maintainability.
Case Study
Consider a simple Calculator class that receives two numbers and an operator string and returns the computed result using a chain of if‑else statements:
public int calculate(int a, int b, String operator) {
int result = Integer.MIN_VALUE;
if ("add".equals(operator)) {
result = a + b;
} else if ("multiply".equals(operator)) {
result = a * b;
} else if ("divide".equals(operator)) {
result = a / b;
} else if ("subtract".equals(operator)) {
result = a - b;
}
return result;
}The same logic can be expressed with a switch statement, but both approaches still suffer from a growing number of branches when new operators are added.
Nested decision structures also make it difficult to manage side effects, such as adding a new operator, which requires modifying the conditional chain.
Refactoring
Design patterns can provide cleaner alternatives to long if‑else chains.
Factory Pattern
By extracting the operation logic into separate classes that implement a common Operation interface, a factory can supply the appropriate implementation based on the operator string.
public interface Operation {
int apply(int a, int b);
}
public class Addition implements Operation {
@Override
public int apply(int a, int b) {
return a + b;
}
}
public class OperatorFactory {
static Map<String, Operation> operationMap = new HashMap<>();
static {
operationMap.put("add", new Addition());
operationMap.put("divide", new Division());
// more operators
}
public static Optional<Operation> getOperation(String operator) {
return Optional.ofNullable(operationMap.get(operator));
}
}
public int calculateUsingFactory(int a, int b, String operator) {
Operation op = OperatorFactory.getOperation(operator)
.orElseThrow(() -> new IllegalArgumentException("Invalid Operator"));
return op.apply(a, b);
}This moves the decision logic into the factory, but the factory itself still contains a map of operators.
Enum‑Based Approach
Enums can encapsulate the behavior for each operator, eliminating the need for external maps.
public enum Operator {
ADD {
@Override
public int apply(int a, int b) { return a + b; }
},
MULTIPLY {
@Override
public int apply(int a, int b) { return a * b; }
};
public abstract int apply(int a, int b);
}
public int calculate(int a, int b, Operator operator) {
return operator.apply(a, b);
}Command Pattern
A Command interface represents an executable action. Concrete command classes (e.g., AddCommand) hold the required data and implement the execution logic.
public interface Command {
Integer execute();
}
public class AddCommand implements Command {
private final int a;
private final int b;
public AddCommand(int a, int b) { this.a = a; this.b = b; }
@Override
public Integer execute() { return a + b; }
}
public int calculate(Command command) {
return command.execute();
}Rule Engine
When many business rules are expressed as conditions, a rule engine can evaluate them independently of the main code flow.
public interface Rule {
boolean evaluate(Expression expression);
Result getResult();
}
public class RuleEngine {
private static List<Rule> rules = new ArrayList<>();
static { rules.add(new AddRule()); }
public Result process(Expression expression) {
Rule rule = rules.stream()
.filter(r -> r.evaluate(expression))
.findFirst()
.orElseThrow(() -> new IllegalArgumentException("Expression does not match any Rule"));
return rule.getResult();
}
}
public class Expression {
private Integer x;
private Integer y;
private Operator operator;
// getters/setters omitted
}
public class AddRule implements Rule {
@Override
public boolean evaluate(Expression e) {
if (e.getOperator() == Operator.ADD) {
// compute result
return true;
}
return false;
}
@Override
public Result getResult() { /* ... */ }
}Conclusion
Various design patterns—Factory, Enum, Command, and Rule Engine—offer viable alternatives to long if‑else chains. The choice of pattern should depend on the specific business context and maintainability requirements.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Big Data Technology & Architecture
Wang Zhiwu, a big data expert, dedicated to sharing big data technology.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
