我有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个案例仍然很长。如何提高该方法的可读性和长度?在这种情况下哪种模式最好?
答案 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);
需要注意的一些关键好处:
NationalSalesTaxPolicy
子类,该子类具有更细微的逻辑。还有更多的改进空间。请注意,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
类,其中包含与某个国家有关的所有内容,例如name
和calculateTax()
方法等,然后您的调用者(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);
}