Search code examples
javacoding-style

How to replace so many if else condition, apart from Switch statement and Polymorphism


Similar to Tax system, I have to write the code where some discount range is given and It is very much possible that in future I will get 10-20 more discount range.

Actual Problem statement: Shopping Cart

There are multiple types of customer(Regular, Premium) they have been given a discount based on their purchase amount. for e.x

Premium Customer

  1. 0-$5000 0%
  2. $5000-$10000 10%
  3. 10000 - above 20%

Regular Customer

  1. 0-$4000 10%
  2. $4000 - $8000 15%
  3. $8000 - $12000 20%
  4. $12000 - above 25%

I tried if-else and switch statement but situation is same. every time I have to touch the already tested method and add either switch-case condition or else-if part or say 10 more classes if polymorphism.

What is the best way to tackle such situation where there are so many condition exists and can be added in future without touching previously written code

public class PremiumCustomerBillingStrategy extends BillingStrategy {

    @Override
    public double calculateFinalBill(double actualBillAmt) {

        double finalBillAmt;

        if (actualBillAmt <= 4000) {
            finalBillAmt = actualBillAmt - getDiscountedAmount(actualBillAmt, PremiumDiscountEnum.BELOW_FOUR_THOUSAND.discount);
        } else if (actualBillAmt > 4000 && actualBillAmt <= 8000) {
            finalBillAmt = actualBillAmt - getDiscountedAmount(actualBillAmt - 4000, PremiumDiscountEnum.BELOW_EIGHT_THOUSAND.discount)
                    - getDiscountedAmount(4000, PremiumDiscountEnum.BELOW_FOUR_THOUSAND.discount);
        } else if (actualBillAmt > 8000 && actualBillAmt <= 12000) {
            finalBillAmt = actualBillAmt - getDiscountedAmount(actualBillAmt - 8000, PremiumDiscountEnum.BELOW_TWELVE_THOUSAND.discount)
                    - getDiscountedAmount(4000, PremiumDiscountEnum.BELOW_EIGHT_THOUSAND.discount)
                    - getDiscountedAmount(4000, PremiumDiscountEnum.BELOW_FOUR_THOUSAND.discount);
        } else {
            finalBillAmt = actualBillAmt - getDiscountedAmount(actualBillAmt - 12000, PremiumDiscountEnum.ABOVE_TWELVE_THOUSAND.discount)
                    - getDiscountedAmount(4000, PremiumDiscountEnum.BELOW_TWELVE_THOUSAND.discount)
                    - getDiscountedAmount(4000, PremiumDiscountEnum.BELOW_EIGHT_THOUSAND.discount)
                    - getDiscountedAmount(4000, PremiumDiscountEnum.BELOW_FOUR_THOUSAND.discount);
        }
        return finalBillAmt;

    }

Solution

  • It would be a bit of an overkill in this case, but if the conditions get really numerous, you could use a class that expresses a function to apply on the amount, if it is within a desired range, like this:

    public class Bills {
    
    class BillFor {
        public final double rangeFrom;
        public final double rangeTo;
        public final Function<Double, Double> calculation;
    
        BillFor(double rangeFrom, double rangeTo, Function<Double, Double> calculation) {
            this.rangeFrom = rangeFrom;
            this.rangeTo = rangeTo;
            this.calculation = calculation;
        }
    }
    
    private final static float MIN = 0f;
    private final static float MAX = Long.MAX_VALUE;
    
    private List<BillFor> billings = List.of(new BillFor(MIN, 4000, i -> i - getDiscountedAmount(i, PremiumDiscountEnum.BELOW_FOUR_THOUSAND.discount)),
                    /*etc*/
                    new BillFor(12000f, MAX, i -> i - 0 /* big expression actually*/));
    
    
    public double calculateFinalBill(double actualBillAmt) {
        return billings.stream()
                .filter(b -> actualBillAmt > b.rangeFrom  && b.rangeTo <= actualBillAmt)
                .findAny()
                .map(b -> b.calculation.apply(actualBillAmt))
                .orElseThrow(IllegalStateException::new);
    }
    }