The tale goes that there is an ancient legacy system, my colleague coded thousands of lines from top to bottom, and each block added several if then java sections to route circumstances, so I feel like I’m trapped in an unfamiliar fairyland.
In this post, I will show you how to optimize the use of if-else, restrict code with too many branching conditions, and make your Java code much easier to read and understand.
Code snippets with too many if-else statements
Before delving into the details of optimization, let’s use a sample demo Java code with multiple if else conditions, and then optimize it in various ways:
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;
}
}
As you can see, the code above calculates shipping charges based on the shipping type
Optimize using Enum
Now, we will use Enumto replace the statements if-else
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);
}
}
public class MainCost {
public static void main(String[] args) {
var calculator = new ShippingCostCalculator();
var cost = calculator.calculateShippingCost(ShippingType.EXPRESS, 2.5);
System.out.println("Shipping cost: " + cost);
}
}
As you can see, the difficult if-else statement has been simplified into two short, straightforward lines of code. Run the main function to see the results.
Advantage :
- Extensibility: Add new shipping types and values. Enumerate and define a handling method for it.
- Maintainable and understandable code: The reasoning for each method of transport is isolated and easy to comprehend.
However , utilizing Enum has certain clear downsides that you should consider:
- Extensibility : Add new shipping types and values. Enumerate and define a handling method for it.
- Difficult to add new parameters : When more parameters are needed, Enum is not well-suited, and the code becomes cumbersome.
- Inheritance restrictions : Enum cannot inherit from other classes, which reduces their potential to reuse logic.
Using Enum for optimization is generally suitable in simple case conditions with few parameters.
Optimization with Factory Pattern
Still the messy code above, we will optimize it in the following way:
Create an Interface ShippingCostStrategy
public interface ShippingCostStrategy {
double calculate(double weight);
}
Next, we will create specific classes for each type of delivery, implementing the interface above.
public class StandardShipping implements ShippingCostStrategy {
@Override
public double calculate(double weight) {
return weight * 5.0;
}
}
public class ExpressShipping implements ShippingCostStrategy {
@Override
public double calculate(double weight) {
return weight * 10.0;
}
}
public class SameDayShipping implements ShippingCostStrategy {
@Override
public double calculate(double weight) {
return weight * 20.0;
}
}
public class InternationalShipping implements ShippingCostStrategy {
@Override
public double calculate(double weight) {
return weight * 50.0;
}
}
public class OvernightShipping implements ShippingCostStrategy {
@Override
public double calculate(double weight) {
return weight * 30.0;
}
}
Now, we will create a Factory class to handle routing to Strategies based on shipping type
import java.util.HashMap;
import java.util.Map;
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;
}
}
Now just call it and use it
public class ShippingCostCalculator {
public double calculateShippingCost(String shippingType, double weight) {
ShippingCostStrategy strategy = ShippingCostFactory.getStrategy(shippingType);
return strategy.calculate(weight);
}
}
Beat 90% of Candidates: Software Engineer's Fast-Track Coding Interview Guide
Advantages of Factory Pattern:
- Easy extensibility : Add new delivery kinds by simply developing additional classes and updating the Factory without changing the core code. Logical separation : Charging logic is segregated, and simple to manage and maintain. Flexibility : The factory might return multiple solutions depending on other factors, increasing flexibility.
Optimization using Strategy Pattern
Before we get into detail, please keep in mind that the implementation will be similar to Factory, but the purpose of use will be slightly different
public interface ShippingCostStrategy {
double calculate(double weight);
}
public class StandardShipping implements ShippingCostStrategy {
@Override
public double calculate(double weight) {
return weight * 5.0;
}
}
public class ExpressShipping implements ShippingCostStrategy {
@Override
public double calculate(double weight) {
return weight * 10.0;
}
}
public class SameDayShipping implements ShippingCostStrategy {
@Override
public double calculate(double weight) {
return weight * 20.0;
}
}
public class InternationalShipping implements ShippingCostStrategy {
@Override
public double calculate(double weight) {
return weight * 50.0;
}
}
public class OvernightShipping implements ShippingCostStrategy {
@Override
public double calculate(double weight) {
return weight * 30.0;
}
}
Now we will create ShippingContext to manage strategies
public class ShippingCostContext {
private ShippingCostStrategy strategy;
public void setStrategy(ShippingCostStrategy strategy) {
this.strategy = strategy;
}
public double calculateShippingCost(double weight) {
return strategy.calculate(weight);
}
}
import java.util.HashMap;
import java.util.Map;
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);
}
}
Now, add it inside the Main Class to use.
public class MainCost {
public static void main(String[] args)
ShippingCostCalculator calculator = new ShippingCostCalculator();
double weight = 10.0;
String shippingType1 = "STANDARD";
double cost1 = calculator.calculateShippingCost(shippingType1, weight);
System.out.println("Shipping cost for " + shippingType1 + ": " + cost1);
String shippingType2 = "EXPRESS";
double cost2 = calculator.calculateShippingCost(shippingType2, weight);
System.out.println("Shipping cost for " + shippingType2 + ": " + cost2);
String shippingType3 = "SAME_DAY";
double cost3 = calculator.calculateShippingCost(shippingType3, weight);
System.out.println("Shipping cost for " + shippingType3 + ": " + cost3);
String shippingType4 = "INTERNATIONAL";
double cost4 = calculator.calculateShippingCost(shippingType4, weight);
System.out.println("Shipping cost for " + shippingType4 + ": " + cost4);
String shippingType5 = "OVERNIGHT";
double cost5 = calculator.calculateShippingCost(shippingType5, weight);
System.out.println("Shipping cost for " + shippingType5 + ": " + cost5);
}
}
In the previous two cases, the Strategy Pattern manages how shipping costs are computed, and the Factory Pattern decides which strategy to utilize based on the delivery type.
Optimize using Stream API and Map
import java.util.HashMap;
import java.util.Map;
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(entry -> entry.getKey().equalsIgnoreCase(shippingType))
.map(Map.Entry::getValue)
.findFirst()
.orElse(0.0)
* weight;
}
public static void main(String[] args) {
ShippingCostCalculator calculator = new ShippingCostCalculator();
double weight = 10.0;
String shippingType1 = "STANDARD";
double cost1 = calculator.calculateShippingCost(shippingType1, weight);
System.out.println("Shipping cost for " + shippingType1 + ": " + cost1);
String shippingType2 = "EXPRESS";
double cost2 = calculator.calculateShippingCost(shippingType2, weight);
System.out.println("Shipping cost for " + shippingType2 + ": " + cost2);
String shippingType3 = "SAME_DAY";
double cost3 = calculator.calculateShippingCost(shippingType3, weight);
System.out.println("Shipping cost for " + shippingType3 + ": " + cost3);
String shippingType4 = "INTERNATIONAL";
double cost4 = calculator.calculateShippingCost(shippingType4, weight);
System.out.println("Shipping cost for " + shippingType4 + ": " + cost4);
String shippingType5 = "OVERNIGHT";
double cost5 = calculator.calculateShippingCost(shippingType5, weight);
System.out.println("Shipping cost for " + shippingType5 + ": " + cost5);
String invalidType = "INVALID";
double invalidCost = calculator.calculateShippingCost(invalidType, weight);
System.out.println("Shipping cost for " + invalidType + ": " + invalidCost);
}
}
This strategy is also quite convenient; while the expansion is not as good as Factory and Strategy, it is still a viable option for simple scenarios.
Thanks, before you go:
👏 Please clap for the story and follow the author 👉
Please share your questions or insights in the comments section below.
Originally published at https://cafeincode.com on August 20, 2024.
Top comments (0)