如何使用许多if语句提高方法的可读性和长度?

时间:2019-06-13 18:17:40

标签: java if-statement design-patterns

我有195个if的方法。这是一个简短的版本:

    <svg #batteryIcon width="95%" height="95%" viewBox="0 0 260 399">
    <defs>
        <linearGradient #batteryLG id="batteryLG" x1="0.5" y1="1" x2="0.5" y2="0">
            <stop offset="0%" stop-opacity="1" stop-color="royalblue" />
            <stop [attr.offset]="value" stop-opacity="1" stop-color="royalblue" />
            <stop [attr.offset]="value" stop-opacity="0" stop-color="royalblue" />
            <stop offset="100%" stop-opacity="0" stop-color="royalblue" />
        </linearGradient>

    </defs>
<svg:rect id="rect" fill="url(#batteryLG)" x="-30" y="0" width=25% height="100%" ></svg:rect>
   </svg>

我可以更改是否要切换:

private BigDecimal calculateTax(String country, BigDecimal amount) throws Exception {
    if(country.equals("POLAND")){
        return new BigDecimal(0.23).multiply(amount);
    }
    else if(country.equals("AUSTRIA")) {
        return new BigDecimal(0.20).multiply(amount);
    }
    else if(country.equals("CYPRUS")) {
        return new BigDecimal(0.19).multiply(amount);
    }
    else {
        throw new Exception("Country not supported");
    }
}

但是195个案例仍然很长。如何提高该方法的可读性和长度?在这种情况下哪种模式最好?

7 个答案:

答案 0 :(得分:43)

创建一个Map<String,Double>来将国家/地区名称映射到其相应的税率:

Map<String,Double> taxRates = new HashMap<> ();
taxRates.put("POLAND",0.23);
...

按如下方式使用Map

private BigDecimal calculateTax(String country, BigDecimal amount) throws Exception {
    if (taxRates.containsKey(country)) {
        return new BigDecimal(taxRates.get(country)).multiply(amount);
    } else {
        throw new Exception("Country not supported");
    }
}

答案 1 :(得分:25)

将数据放入XML文件或数据库中,然后使用其填充字典。这样,您可以轻松更改数据,并将数据与应用程序逻辑分离。或者,只需在您的方法中执行SQL查询即可。

答案 2 :(得分:14)

不要这样做!

目前,您的calculateTax方法就像一个容器,用于容纳四个实际的calculateTax方法,三个国家/地区中的每个国家/地区,以及一个无效案例中的一个。您沿着这些思路制作的所有其他方法都将是这样。按照这种模式,您将在许多方法中最终进行许多切换(检查一组相同的案例),其中每个案例都包含案例的详细信息。但这确实是多态的一种更好的方式!

这样的模式非常有力地表明您没有利用面向对象的优势,并且除非有其他原因,否则您绝对应该这样做。毕竟是Java,这就是整个过程。

创建类似于TaxPolicy的界面:

interface TaxPolicy {
    BigDecimal calculateTaxFor(BigDecimal saleAmount);
}

创建一个实现它的类:

class NationalSalesTaxPolicy implements TaxPolicy  {
    String countryName;
    BigDecimal salesTaxRate;

    // Insert constructor, getters, setters, etc. here

    BigDecimal calculateTaxFor(BigDecimal saleAmount) {
        return saleAmount.multiply(salesTaxRate);         
    }
}

然后,创建此类的对象,每个国家/地区要支持一个对象。我们可以将此列表包装到一个新的类NationalSalesTaxCalculator中,该类将成为我们计算任何国家/地区的营业税的一站式服务:

class NationalSalesTaxCalculator {
    static Map<String, NationalSalesTaxPolicy> SUPPORTED_COUNTRIES = Stream.of(
        new NationalSalesTaxPolicy("POLAND", "0.23"),
        new NationalSalesTaxPolicy("AUSTRIA", "0.20"),
        new NationalSalesTaxPolicy("CYPRUS", "0.19")
    ).collect(Collectors.toMap(NationalSalesTaxPolicy::getCountryName, c -> c));

    BigDecimal calculateTaxFor(String countryName, BigDecimal saleAmount) {
        NationalSalesTaxPolicy country = SUPPORTED_COUNTRIES.get(countryName);
        if (country == null) throw new UnsupportedOperationException("Country not supported");

        return country.calculateTaxFor(saleAmount);
    }
}

我们可以像这样使用它:

NationalSalesTaxCalculator calculator = new NationalSalesTaxCalculator();
BigDecimal salesTax = calculator.calculateTaxFor("AUSTRIA", new BigDecimal("100"));
System.out.println(salesTax);

需要注意的一些关键好处:

  1. 如果添加要支持的新国家,只需创建一个新对象。所有可能需要该对象的方法都会自动“做正确的事”,而无需手动查找所有方法,以便添加新的if语句。
  2. 您有空间根据需要调整功能。例如,我住的地方(加拿大安大略省)不对杂货收取营业税。因此,我可以创建自己的NationalSalesTaxPolicy子类,该子类具有更细微的逻辑。
  3. 还有更多的改进空间。请注意,NationalSalesTaxCalculator.calculateTaxFor()包含一些特定于处理不受支持的国家/地区的代码。如果我们在该类中添加新的操作,则每个方法都需要相同的null检查和错误抛出。

    相反,可以将其重构为使用null object pattern。您实现了UnsuppoertedTaxPolicy,这是一个 通过抛出异常来实现所有接口方法。像这样:

    class UnsuppoertedTaxPolicy implements TaxPolicy {
        public BigDecimal calculateTaxFor(BigDecimal saleAmount) {
            throw new UnsupportedOperationException("Country not supported");
        }
    }
    

    然后您可以

    TaxPolicy countryTaxPolicy = Optional
        .ofNullable(SUPPORTED_COUNTRIES.get(countryName))
        .orElse(UNSUPPORTED_COUNTRY);
    return countryTaxPolicy.calculateTaxFor(saleAmount);
    

    此操作将所有异常“集中”到一个位置,这使它们更易于查找(因此更容易设置断点),更容易编辑(以防您要迁移异常类型或更改消息) ,并且整理了其余的代码,因此只需要担心这种情况。

这是一个有效的演示:https://repl.it/@alexandermomchilov/Polymorphism-over-ifswitch

答案 3 :(得分:8)

作为框架挑战...

195个案例不太长 if ,很清楚他们在做什么,为什么做, if 每种情况下的代码最少。是的,它很长,但是完全可读,因为您确切知道它在做什么。长度不一定表示无法读取。

当然,正如其他答案所说,这可能是代码气味,表明您没有正确使用OO。但就其本身而言,它只是很长,并非难以理解。

答案 4 :(得分:5)

如果值是恒定的并且不应该定期更改(我对此表示怀疑)。我将使用枚举介绍一个静态元模型:

public enum CountryList {

    AUSTRIA(BigDecimal.valueOf(0.20)),
    CYPRUS(BigDecimal.valueOf(0.19)),
    POLAND(BigDecimal.valueOf(0.23));

    private final BigDecimal countryTax;

    CountryList(BigDecimal countryTax) {
        this.countryTax = countryTax;
    }

    public BigDecimal getCountryTax() {
        return countryTax;
    }

    public static BigDecimal countryTaxOf(String countryName) {
        CountryList country = Arrays.stream(CountryList.values())
                .filter(c -> c.name().equalsIgnoreCase(countryName))
                .findAny()
                .orElseThrow(() -> new IllegalArgumentException("Country is not found in the dictionary: " + countryName));

        return country.getCountryTax();
    }
}

然后

private BigDecimal calculateTax(String country, BigDecimal amount) throws Exception {
    return CountryList.countryTaxOf(country).multiply(amount);
}

它可读性强,编译时安全,易于扩展,每个国家/地区都有更多元数据,而且样板更少。

答案 5 :(得分:4)

编辑:错过了@Alexander的答案;可能有点矫kill过正,但他也达到了要点:使用OOP。
编辑2:实施@Luaan的建议

我可能遗漏了一些明显的东西,并且在后期可能难以实现,但这对我来说似乎是面向对象编程的完美案例:

您创建一个Country类,其中包含与某个国家有关的所有内容,例如namecalculateTax()方法等,然后您的调用者(calculateTotalAmount()或其他任何方式)将调用country.calculateTax(amount)而不是calculateTax(country, amount),整个if / switch构造就消失了。

此外,当您增加对一个新国家的支持时(例如,那里发生了另一场内战,一个国家分裂了),您只需将新国家的所有内容都添加到一个地方,而不必使用巨大的{ {1}}个链或if()个...

答案 6 :(得分:2)

如果您坚持使用switch:自JDK 12起,switch表达式通过允许使用多个switch标签和lambda样式,可以潜在地提高case语句的可读性和长度。语法:

private BigDecimal calculateTax(String country, BigDecimal amount) throws Exception {
    double rate = switch(country) {
        case "POLAND", "IRELAND" -> 0.23;
        case "AUSTRIA", "FRANCE" -> 0.20;
        case "CYPRUS", "GERMANY" -> 0.19;
        default -> throw new Exception("Country not supported");
    };
    return new BigDecimal(rate).multiply(amount);
}