Java 如何使用多个if语句提高方法的可读性和长度?
我有一个195个ifs的方法。以下是一个简短的版本:Java 如何使用多个if语句提高方法的可读性和长度?,java,if-statement,design-patterns,Java,If Statement,Design Patterns,我有一个195个ifs的方法。以下是一个简短的版本: 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")) {
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");
}
}
我可以将ifs更改为交换机:
private BigDecimal calculateTax(String country, BigDecimal amount) throws Exception {
switch (country) {
case "POLAND":
return new BigDecimal(0.23).multiply(amount);
case "AUSTRIA":
return new BigDecimal(0.20).multiply(amount);
case "CYPRUS":
return new BigDecimal(0.19).multiply(amount);
default:
throw new Exception("Country not supported");
}
}
但195个病例仍然很长。如何提高该方法的可读性和长度?在这种情况下,哪种模式最好?创建一个将国家名称映射到其相应税率的地图:
Map<String,Double> taxRates = new HashMap<> ();
taxRates.put("POLAND",0.23);
...
将数据放入XML文件或数据库中,然后使用它填充字典。这样,您可以轻松地更改数据,并将数据与应用程序逻辑分离。或者,只需在方法中执行SQL查询即可。如果值是常量且不需要定期更改(我对此表示怀疑)。我将介绍一个使用Enum的静态元模型:
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();
}
}
然后
它可读,编译时安全,易于扩展,每个国家都有额外的元数据,而且样板文件更少。不要这样做强>
现在,您的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);
}
}
需要注意的一些关键好处:
如果您添加了一个想要支持的新国家,您只需创建一个新对象。所有可能需要该对象的方法都会自动“做正确的事情”,而无需手动查找它们,以便添加新的if语句
您可以根据需要调整功能。例如,在我居住的地方(加拿大安大略省),食品杂货不征收销售税。因此,我可以创建自己的NationalSalesTaxPolicy
子类,该子类具有更细微的逻辑
还有更大的改进空间。请注意,NationalSalesTaxCalculator.calculateTaxFor()
包含一些特定于处理不受支持国家/地区的代码。如果我们向该类添加新操作,每个方法都需要相同的空检查和错误抛出
相反,可以将其重构为使用。您实现了一个不受支持的taxpolicy
,该类
通过抛出异常来实现所有接口方法。像这样:
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);
这会将所有异常“集中”到一个位置,从而使它们更容易找到(因此更容易设置断点),更容易编辑(以防您想要迁移异常类型或更改消息),并且它会将其余代码分离,因此只需担心满意的情况
这里有一个有效的演示:编辑:错过了@Alexander的答案;这可能有点过分,但他也抓住了要点:使用OOP。
编辑2:实施@Luaan的建议
我可能遗漏了一些明显的东西,在这么晚的阶段实现起来可能有点困难,但在我看来,这是一个面向对象编程的完美案例:
您创建了一个Country
类,该类包含与某个国家有关的所有内容,例如name
和calculateAX()
方法等等,然后您的调用者(calculateTotalAmount()
,或其他什么)将调用Country.calculateAX(amount)
,而不是calculateAX(Country,amount)
,整个if/switch结构刚刚消失
此外,当你增加对一个新国家的支持时(比如说,某个地方发生了另一场内战,一个国家分裂了),你只需在一个地方为这个新国家添加所有东西,而不是用巨大的if()
链或switch()
es…作为一个框架挑战来寻找无数方法
195个案例并不太长if很清楚他们在做什么以及为什么,并且if每个案例中的代码都很小。是的,它很长,但它的可读性很好,因为你确切地知道它在做什么。长度并不一定意味着不可读
当然,正如其他答案所说,这可能是一种代码气味,表明您没有正确使用OO。但就其本身而言,它很长,不是不可读的。如果您坚持使用switch
:自JDK 12以来,switch
表达式通过允许多个case
标签和lambda样式的语法,有可能提高switch
语句的可读性和长度:
private BigDecimal calculateTax(字符串国家/地区,BigDecimal金额)引发异常{
双费率=开关(国家/地区){
案例“波兰”、“爱尔兰”->0.23;
案例“奥地利”、“法国”->0.20;
案例“塞浦路斯”、“德国”->0.19;
默认->抛出新异常(“不支持的国家”);
};
返回新的BigDecimal(比率)。乘以(金额);
}
使用java.util.Map
?是的,地图或字典可能是你在这里的最佳选择。警告:避免将浮点数字与BigDecimal一起使用。BigDecimal(0.23)可能与BigDecimal(23)/BigDecimal(100)不同。后者是23%的正确代表。我投票结束这个问题,因为关于提高现有工作代码可读性的问题属于我@
NationalSalesTaxCalculator calculator = new NationalSalesTaxCalculator();
BigDecimal salesTax = calculator.calculateTaxFor("AUSTRIA", new BigDecimal("100"));
System.out.println(salesTax);
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);