Chuyện là có một hệ thống cũ legacy từ trước, đồng nghiệp code cả nghìn dòng từ trên xuống dưới, mỗi khối lại đắp thêm rất nhiều đoạn if else java để routing điều kiện, đọc vào mà cảm giác như đang lạc vào chốn bồng lai tiên cảnh nào đó không rõ nữa.

Bài viết này mình sẽ giúp các bạn tối ưu lại việc sử dụng if else, hạn chế những đoạn code có quá nhiều điều kiện rẽ nhánh và làm cho code java của các bạn có thể dễ đọc, tường minh hơn rất nhiều.

Những đoạn code có quá nhiều điều kiện if-else

Trước khi đi vào chi tiết việc tối ưu thì chúng ta hãy dùng một đoạn code java demo mẫu đang có nhiều điều kiện if else, từ đó sẽ tối ưu đoạn code đó theo các phương án khác nhau:

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;
    }
}

Như các bạn có thể thấy, đoạn code trên làm nhiệm vụ tính toán chi phí vận chuyển dựa trên loại giao hàng

Tối ưu bằng việc sử dụng Enum

Bây giờ, chúng ta sẽ sử dụng Enum để thay thế các câu lệnh if-else như sau:

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);
    }

}

Các bạn thấy, giờ đoạn if-else loằng ngoằng đã trở thành hai dòng code khá ngắn, tiện lợi, chạy hàm main để xem kết quả

Ưu điểm

  • Tính mở rộng: Nếu có thêm loại vận chuyển mới, chỉ cần thêm giá trị mới vào Enum, định nghĩa thêm phương thức xử lý cho nó
  • Code dễ bảo trì và dễ đọc: Các logic liên quan đến từng loại vận chuyển được tách biệt và dễ theo dõi.

Tuy nhiên thì việc sử dụng Enum cũng đem đến một số nhược điểm rõ ràng để các bạn có thể cân nhắc:

  • Phức tạp hơn khi logic phức tạp: Nếu logic xử lý có nhiều tham số số, Enum làm cho code trở nên cồng kềnh và khó quản lý.
  • Khó mở rộng với tham số mới: Khi cần thêm nhiều tham số, Enum không phù hợp cho lắm, làm cho mã trở nên phức tạp
  • Hạn chế kế thừa: Enum không thể kế thừa từ lớp khác, làm giảm khả năng tái sử dụng logic.

Nói chung là dùng Enum để tối ưu thì sẽ phù hợp trong những điều kiện case đơn giản và ít tham số

Tối ưu bằng Factory Parttern

Vẫn là đoạn code bùng nhùng ở trên, chúng ta sẽ lần lượt tối ưu bằng cách sau:

Tạo interface cho các chiến lược tính phí vận chuyển

public interface ShippingCostStrategy {
    double calculate(double weight);
}

Tiếp theo chúng ta sẽ tạo các lớp cụ thể cho từng loại giao hàng, implement interface ở trên

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;
    }
}

Giờ sẽ tạo một class Factory để xử lý routing ra các Strategy dựa theo shippingType

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;
    }
}

Giờ chỉ việc gọi ra mà sử dụng thôi, rất đơn giản

public class ShippingCostCalculator {
    public double calculateShippingCost(String shippingType, double weight) {
        ShippingCostStrategy strategy = ShippingCostFactory.getStrategy(shippingType);
        return strategy.calculate(weight);
    }
}

Ưu điểm của Factory Pattern:

  • Mở rộng dễ dàng: Thêm loại giao hàng mới chỉ cần tạo thêm lớp và cập nhật Factory mà không cần sửa code chính
  • Tách biệt logic: Logic tính phí được tách biệt, dễ quản lý và bảo trì
  • Linh hoạt: Factory có thể trả về chiến lược khác nhau dựa trên các điều kiện khác, giúp tăng tính linh hoạt

Tối ưu bằng Strategy Pattern

Trước khi đi vào chi tiết thì các bạn chú ý, sự khác nhau về mặt triển khai sẽ không có nhiều khác biệt so với Factory, tuy nhiên mục đích sử dụng lại sẽ khác nhau đôi chút

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;
    }
}

Đến đây, chúng ta sẽ tạo ShippingContext để quản lý các chiến lược (Strategy)

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);
    }
}

Giờ đem vào Main Class để sử dụng



public class MainCost {

    public static void main(String[] args) 
        ShippingCostCalculator calculator = new ShippingCostCalculator();

        double weight = 10.0; // Trọng lượng của hàng hóa

        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);
    }


}

Trong hai ví dụ trên Strategy Pattern quản lý cách mà phí vận chuyển được tính toán, trong khi Factory Pattern quyết định chiến lược nào sẽ được sử dụng dựa trên loại giao hàng.

Tối ưu bằng cách sử dụng Stream API và 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; // Trả về 0.0 nếu loại giao hàng không hợp lệ
    }

    public static void main(String[] args) {
        ShippingCostCalculator calculator = new ShippingCostCalculator();

        double weight = 10.0; // Trọng lượng hàng hóa

        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);
    }
}

Cách này cũng là một phương án khá tiện lợi, tuy việc mở rộng thì không bằng được Factory và Strategy, nhưng cũng là một phương án cho những trường hợp đơn giản.

Xem thêm một số bài viết nổi bật dưới đây: