对枚举策略方法的疑问
在计算所得税时,我尝试使用枚举策略方法使逻辑更加简洁。但最终我不满意,主要是因为我必须为收入群体、限制和费率编写不同的枚举。这是因为它们都是不同性质的常数。但正因为如此,代码看起来并不紧凑,而且似乎缺乏封装。
我的担心是真的吗?请让我知道您的看法或更好的方法。
比如说,所得税组别和相应的税率如下:
- 收入在0 – 500000之间:10% 应税
- 收入 500001 – 1000000 : 20% 应税
- 收入之间 1000001 – 无限:应纳税 30%
例如,收入为 1200000 时适用的所得税为 200000 的 30% (1200000 – 1000000) + 500000 的 20% (1000000 – 500000) + 500000 的 10% (500000 – 0) = 160000
代码:
package itcalc;
public class IncomeTaxCalculatorImpl {
private enum IncomeGroup {
ONE(Limit.GROUP_ONE_LIMIT) {
@Override
public double calculate(double income) {
return income * Rate.GROUP_ONE_RATE.value / 100;
}
},
TWO(Limit.GROUP_TWO_LIMIT) {
@Override
public double calculate(double income) {
return ((income - Limit.GROUP_ONE_LIMIT.maximum)
* Rate.GROUP_TWO_RATE.value / 100)
+ ONE.calculate(Limit.GROUP_ONE_LIMIT.maximum);
}
},
THREE(Limit.GROUP_THREE_LIMIT) {
@Override
public double calculate(double income) {
return ((income - Limit.GROUP_TWO_LIMIT.maximum)
* Rate.GROUP_THREE_RATE.value / 100)
+ TWO.calculate(Limit.GROUP_TWO_LIMIT.maximum
- Limit.GROUP_ONE_LIMIT.maximum)
+ ONE.calculate(Limit.GROUP_ONE_LIMIT.maximum);
}
};
private final Limit limit;
private enum Limit {
GROUP_ONE_LIMIT(0, 500000),
GROUP_TWO_LIMIT(500001, 1000000),
GROUP_THREE_LIMIT(1000001, Double.MAX_VALUE);
private final double minimum;
private final double maximum;
private Limit(double minimum, double maximum) {
this.minimum = minimum;
this.maximum = maximum;
}
}
private enum Rate {
GROUP_ONE_RATE(10), GROUP_TWO_RATE(20), GROUP_THREE_RATE(30);
private final double value;
private Rate(double value) {
this.value = value;
}
}
private IncomeGroup(Limit limit) {
this.limit = limit;
}
abstract double calculate(double income);
}
public double calculate(double income) {
for (IncomeGroup group : IncomeGroup.values()) {
if (income >= group.limit.minimum
&& income <= group.limit.maximum) {
return group.calculate(income);
}
}
throw new IllegalArgumentException("Invalid Income Value");
}
}
For computing Income Tax, I tried to use Enum Strategy approach to make the logic more concise. But in the end I’m not satisfied mainly because I had to write different enums for income groups, limits and rates. This was because they are all constants of different nature. But due to this the code doesn’t looks compact and seems to lack encapsulation.
Is my worry genuine? Please let me know your views or a better approach.
Say, Income Tax groups and corresponding rates are as follows:
- Income between 0 – 500000 : 10%
taxable - Income between 500001 –
1000000 : 20% taxable - Income between
1000001 – Infinite : 30% taxable
For example, Income Tax applicable on an income of 1200000 would be
30 % of 200000 (1200000 – 1000000)
+ 20% of 500000 (1000000 – 500000)
+ 10% of 500000 (500000 – 0)
= 160000
Code:
package itcalc;
public class IncomeTaxCalculatorImpl {
private enum IncomeGroup {
ONE(Limit.GROUP_ONE_LIMIT) {
@Override
public double calculate(double income) {
return income * Rate.GROUP_ONE_RATE.value / 100;
}
},
TWO(Limit.GROUP_TWO_LIMIT) {
@Override
public double calculate(double income) {
return ((income - Limit.GROUP_ONE_LIMIT.maximum)
* Rate.GROUP_TWO_RATE.value / 100)
+ ONE.calculate(Limit.GROUP_ONE_LIMIT.maximum);
}
},
THREE(Limit.GROUP_THREE_LIMIT) {
@Override
public double calculate(double income) {
return ((income - Limit.GROUP_TWO_LIMIT.maximum)
* Rate.GROUP_THREE_RATE.value / 100)
+ TWO.calculate(Limit.GROUP_TWO_LIMIT.maximum
- Limit.GROUP_ONE_LIMIT.maximum)
+ ONE.calculate(Limit.GROUP_ONE_LIMIT.maximum);
}
};
private final Limit limit;
private enum Limit {
GROUP_ONE_LIMIT(0, 500000),
GROUP_TWO_LIMIT(500001, 1000000),
GROUP_THREE_LIMIT(1000001, Double.MAX_VALUE);
private final double minimum;
private final double maximum;
private Limit(double minimum, double maximum) {
this.minimum = minimum;
this.maximum = maximum;
}
}
private enum Rate {
GROUP_ONE_RATE(10), GROUP_TWO_RATE(20), GROUP_THREE_RATE(30);
private final double value;
private Rate(double value) {
this.value = value;
}
}
private IncomeGroup(Limit limit) {
this.limit = limit;
}
abstract double calculate(double income);
}
public double calculate(double income) {
for (IncomeGroup group : IncomeGroup.values()) {
if (income >= group.limit.minimum
&& income <= group.limit.maximum) {
return group.calculate(income);
}
}
throw new IllegalArgumentException("Invalid Income Value");
}
}
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(4)
您不需要 Limit 或 Rate 类别。您可以看出这一点,因为 Rate、Limit 和 IncomeGroup 之间存在一一对应关系 - 将它们代表的值合并到 IncomeGroup 中,从而为该组提供三个值:两个表示范围,一个表示百分比。即使您在其他地方使用这些值,您也可以执行此操作 - 只需将它们设为公共枚举的最终字段即可。
顺便说一句,如果您使用双精度数,则范围的最小值应该是较低范围的最大值,而不是最大值加一。税表通常给出这些值,因为它们将收入四舍五入到最接近的美元。如果您不这样做,您可能会获得不属于任何范围的收入。
更顺便说一句,calculate() 可能应该是静态的,这样您就可以免去计算自己属于哪个收入组的麻烦。
You don't need the Limit or Rate classes. You can tell this because there is a one-to-one correspondance between the Rate, Limit and IncomeGroup - incorporate the values they represent into IncomeGroup, giving you three values for the group: two for the range, one for the percentage rate. You can do this even if you are using the values elsewhere - just make them public final fields of the Enums.
Incidentally if you are using doubles then the minimum for a range should be the maximum of the lower range, not the maximum plus one. Tax forms usually give these values because they round incomes to the nearest dollar. If you're not doing this you may get an income that falls into none of the ranges.
Even more incidentally, calculate() should probably be static, saving you the trouble of working out which income group you are in.
正如我对税收计算的期望,随着时间的推移,您可能需要考虑越来越多的事实。例如,定义收入组的范围可能需要从数据库加载,因为这些范围可能会改变。同样,特定收入群体的适用税率也可能发生变化。将这些范围硬编码到您的代码中不是一个好主意!
您可能想了解规则引擎(例如前向链接)背后的想法。 JBoss 的 Drools 手册很好地概述了如何使用规则引擎(以及在大多数规则引擎之间传递的想法)。
无论如何,我认为枚举策略模式适用于更简单的业务案例(例如打印给定的对象,例如
org.joda.time.format.DateTimeFormatter
),但不适用于可能需要的更复杂的情况聚合来自多个对象的事实以计算结果(例如工资、税收、调节账簿等的计算)As I would expect for tax calculations, you will probably need to account for more and more facts over time. E.g. the ranges defining income groups might need to be loaded from a database since these could be change. Likewise the applicable rate for a given income group could change. Not a good idea to hard code these ranges into your code!!
You may want to look at the ideas behind a rules engine such as forward chaining. JBoss's Drools manual does a good job of sketching how you can use a rules engine (and the ideas transfer across most rules engine).
At any rate, I think the enum strategy pattern works for a simpler business cases (e.g. printing a given object such as
org.joda.time.format.DateTimeFormatter
) but not for more complex cases which may need to aggregate facts from multiple objects to calculate a result (e.g. calculations for payroll, taxes, reconciling books, etc.)Limit
和Rate
枚举是否在其他地方重用?如果没有,您可以摆脱它们并修改 IncomeGroup 以具有限制和费率元素:这样,您将拥有更少的枚举,并且代码将更加紧凑
Are
Limit
andRate
enums being reused elsewhere? If not, you can get rid of them and modify IncomeGroup to have the limit and rate elements:This way, you will have little less enums and the code will be little more compact
枚举对于使代码更具可读性很有用,但我想您的代码通常不会单独引用常量一、二或三,它更有可能需要在执行计算时迭代所有税组。
如果将税收组常量与税收计算算法分开,代码可能会更清晰。使用对象数组,您可以编写:
然后您可以编写一个算法来计算税款,而不是维护三个单独的算术副本:
Enums are useful for making code more readable, but I imagine your code is not often going to refer to constants ONE, TWO or THREE in isolation, it's more likely to need to iterate through all the tax groups while performing a calculation.
The code would probably be clearer if you separate the tax group constants from the tax calculation algorithm. Using an array of objects, you can write:
You can then write a single algorithm to calculate the tax, rather than maintaining three separate copies of the arithmetic: