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.

21CTO
21CTO
21CTO
How to Refactor Complex if‑else Chains in Java: Enums, Factories & Strategies

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.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

Design PatternsJavaenumrefactoringstrategyFactoryif-else
21CTO
Written by

21CTO

21CTO (21CTO.com) offers developers community, training, and services, making it your go‑to learning and service platform.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.