How to Refactor Complex if‑else Chains in Java: Enums, Factories & Strategies
This article explains why excessive if‑else statements increase cost and complexity in Java applications and demonstrates several refactoring techniques—including enums, the factory pattern, the strategy pattern, and Stream API maps—to produce cleaner, more maintainable code.
Introduction
The article introduces the problem of overusing if‑else statements in Java, describing how they can lead to debugging difficulties, technical debt, and reduced code quality.
Original Example
A straightforward implementation that calculates shipping cost based on a string shippingType and weight is shown.
public class ShippingCostCalculator {
public double calculateShippingCost(String shippingType, double weight) {
if (shippingType.equals("STANDARD")) {
return weight * 5.0;
} else if (shippingType.equals("EXPRESS")) {
return weight * 10.0;
} else if (shippingType.equals("SAME_DAY")) {
return weight * 20.0;
} else if (shippingType.equals("INTERNATIONAL")) {
return weight * 50.0;
} else if (shippingType.equals("OVERNIGHT")) {
return weight * 30.0;
}
return 0;
}
}Refactoring with Enum
Replacing the conditional chain with an enum that encapsulates the cost calculation for each shipping type.
public enum ShippingType {
STANDARD {
@Override
public double getCost(double weight) { return weight * 5.0; }
},
EXPRESS {
@Override
public double getCost(double weight) { return weight * 10.0; }
},
SAME_DAY {
@Override
public double getCost(double weight) { return weight * 20.0; }
},
INTERNATIONAL {
@Override
public double getCost(double weight) { return weight * 50.0; }
},
OVERNIGHT {
@Override
public double getCost(double weight) { return weight * 30.0; }
};
public abstract double getCost(double weight);
} public class ShippingCostCalculator {
public double calculateShippingCost(ShippingType shippingType, double weight) {
return shippingType.getCost(weight);
}
}Refactoring with Factory Pattern
Define a ShippingCostStrategy interface and concrete strategy classes, then use a factory to obtain the appropriate strategy.
public interface ShippingCostStrategy {
double calculate(double weight);
} public class ShippingCostFactory {
private static final Map<String, ShippingCostStrategy> strategies = new HashMap<>();
static {
strategies.put("STANDARD", new StandardShipping());
strategies.put("EXPRESS", new ExpressShipping());
strategies.put("SAME_DAY", new SameDayShipping());
strategies.put("INTERNATIONAL", new InternationalShipping());
strategies.put("OVERNIGHT", new OvernightShipping());
}
public static ShippingCostStrategy getStrategy(String shippingType) {
ShippingCostStrategy strategy = strategies.get(shippingType);
if (strategy == null) {
throw new IllegalArgumentException("Invalid shipping type: " + shippingType);
}
return strategy;
}
} public class ShippingCostCalculator {
public double calculateShippingCost(String shippingType, double weight) {
ShippingCostStrategy strategy = ShippingCostFactory.getStrategy(shippingType);
return strategy.calculate(weight);
}
}Refactoring with Strategy Pattern
Similar to the factory approach but the context explicitly holds a strategy instance.
public class ShippingCostContext {
private ShippingCostStrategy strategy;
public void setStrategy(ShippingCostStrategy strategy) { this.strategy = strategy; }
public double calculateShippingCost(double weight) { return strategy.calculate(weight); }
} public class ShippingCostCalculator {
private static final Map<String, ShippingCostStrategy> strategies = new HashMap<>();
static {
strategies.put("STANDARD", new StandardShipping());
strategies.put("EXPRESS", new ExpressShipping());
strategies.put("SAME_DAY", new SameDayShipping());
strategies.put("INTERNATIONAL", new InternationalShipping());
strategies.put("OVERNIGHT", new OvernightShipping());
}
private final ShippingCostContext context = new ShippingCostContext();
public double calculateShippingCost(String shippingType, double weight) {
ShippingCostStrategy strategy = strategies.get(shippingType);
if (strategy == null) {
throw new IllegalArgumentException("Invalid shipping type: " + shippingType);
}
context.setStrategy(strategy);
return context.calculateShippingCost(weight);
}
}Refactoring with Stream API and Map
A concise solution that stores per‑type rates in a map and uses the Stream API to look up the appropriate rate.
public class ShippingCostCalculator {
private static final Map<String, Double> shippingCosts = new HashMap<>();
static {
shippingCosts.put("STANDARD", 5.0);
shippingCosts.put("EXPRESS", 10.0);
shippingCosts.put("SAME_DAY", 20.0);
shippingCosts.put("INTERNATIONAL", 50.0);
shippingCosts.put("OVERNIGHT", 30.0);
}
public double calculateShippingCost(String shippingType, double weight) {
return shippingCosts.entrySet().stream()
.filter(e -> e.getKey().equalsIgnoreCase(shippingType))
.map(Map.Entry::getValue)
.findFirst()
.orElse(0.0) * weight;
}
}Pros and Cons
Enum: simple and type‑safe but hard to extend with additional parameters.
Factory: easy to add new shipping types without touching core logic.
Strategy: separates algorithm from usage, improving flexibility.
Stream‑Map: concise for small, static sets of rates.
Conclusion
Replacing long if‑else chains with enums, factories, strategies, or map‑based lookups makes Java code more readable, maintainable, and extensible, especially in backend services that calculate shipping costs or similar business rules.
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.
21CTO
21CTO (21CTO.com) offers developers community, training, and services, making it your go‑to learning and service platform.
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.
