Mic*_*u93 26 java design-patterns if-statement
我有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")) {
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");
}
}
Run Code Online (Sandbox Code Playgroud)
我可以将ifs更改为switch:
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");
}
}
Run Code Online (Sandbox Code Playgroud)
但是195个案例仍然很长。如何提高该方法的可读性和长度?在这种情况下哪种模式最好?
Era*_*ran 44
创建一个Map<String,Double>将国名映射到其相应税率的:
Map<String,Double> taxRates = new HashMap<> ();
taxRates.put("POLAND",0.23);
...
Run Code Online (Sandbox Code Playgroud)
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");
}
}
Run Code Online (Sandbox Code Playgroud)
Ale*_*ica 14
Don't do this!
As it is right now, your calculateTax method is like a container for four actual calculateTax methods, one for each of the 3 countries, and one for the invalid case. Every other method you make along these lines will be like that. Following this pattern, you'll end up with many switches (checking for the same set of cases) within many methods, where each case contains the specifics of a case. But that's exactly polymorphism does, in a much better way!
Patterns like this are a very strong indication that you're not taking advantage of object orientation, and barring any other reasons not to, you definitely should. It's Java after all, and that's kind of the whole schtick.
Create an interface like TaxPolicy:
interface TaxPolicy {
BigDecimal calculateTaxFor(BigDecimal saleAmount);
}
Run Code Online (Sandbox Code Playgroud)
Create a class that implements it:
class NationalSalesTaxPolicy implements TaxPolicy {
String countryName;
BigDecimal salesTaxRate;
// Insert constructor, getters, setters, etc. here
BigDecimal calculateTaxFor(BigDecimal saleAmount) {
return saleAmount.multiply(salesTaxRate);
}
}
Run Code Online (Sandbox Code Playgroud)
Then, create objects of this class, one per country you wish to support. We can wrap this list into a new class, NationalSalesTaxCalculator, which will be our one-stop-shop for calculating sales tax for any country:
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);
}
}
Run Code Online (Sandbox Code Playgroud)
And we can use it like:
NationalSalesTaxCalculator calculator = new NationalSalesTaxCalculator();
BigDecimal salesTax = calculator.calculateTaxFor("AUSTRIA", new BigDecimal("100"));
System.out.println(salesTax);
Run Code Online (Sandbox Code Playgroud)
Some key benefits to notice:
NationalSalesTaxPolicy that has more nuanced logic.There's even some more room for improvement. Notice that NationalSalesTaxCalculator.calculateTaxFor() contains some code specific to handling an unsupported country. If we add in new operations to that class, every method would need the same null check and error throw.
Instead, that could be refactored into using the null object pattern. You implement an UnsuppoertedTaxPolicy, which is a class that
implements all interface methods by throwing exceptions. Like so:
class UnsuppoertedTaxPolicy implements TaxPolicy {
public BigDecimal calculateTaxFor(BigDecimal saleAmount) {
throw new UnsupportedOperationException("Country not supported");
}
}
Run Code Online (Sandbox Code Playgroud)
You can then do
TaxPolicy countryTaxPolicy = Optional
.ofNullable(SUPPORTED_COUNTRIES.get(countryName))
.orElse(UNSUPPORTED_COUNTRY);
return countryTaxPolicy.calculateTaxFor(saleAmount);
Run Code Online (Sandbox Code Playgroud)
This "centralizes" all your exceptions into one place, which makes them easier to find (thus easier to set break points on), easier to edit (in case you ever want to migrate the exception types, or change the message), and it declutters the rest of the code, so that it only needs to worry about the happy case.
Here's a working demo: https://repl.it/@alexandermomchilov/Polymorphism-over-ifswitch
作为框架挑战...
如果清楚他们在做什么,为什么做以及每个案例中的代码最少,那么 195个案例就不会太长。是的,它很长,但是完全可读,因为您确切知道它在做什么。长度不一定意味着无法读取。
正如其他答案所言,这可能是代码气味,表明您没有正确使用OO。但就其本身而言,它只是很长,并非难以理解。
如果值是恒定的并且不应该定期更改(我对此表示怀疑)。我将使用枚举介绍一个静态元模型:
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();
}
}
Run Code Online (Sandbox Code Playgroud)
然后
private BigDecimal calculateTax(String country, BigDecimal amount) throws Exception {
return CountryList.countryTaxOf(country).multiply(amount);
}
Run Code Online (Sandbox Code Playgroud)
它可读性强,编译时安全,易于扩展,每个国家/地区都可以提供更多的元数据,样板更少。