Benjy's Blog

策略枚举

2015-02-27

搞 Java 的一定听说过设计模式中的策略模式,具体这个模式的优点我就不细说了,这里讲的并非策略模式而是和策略模式类同却比策略模式简便的东西————策略枚举(strategy enum)

举个栗子

考虑用一个枚举表示薪资包中的工作天数,这个枚举有一个方法,根据给定某工人的基本工资(按小时)以及当天的工作时间,来计算他当天的报酬。在五个工作日中,超过正常八个小时的工作时间都会产生加班工资;在双休日中,所有的工作都产生加班工资。利用 switch 语句,很容易将多个 case 标签分别应用到两个代码片段中,来完成这一计算。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
enum PayrollDay {
MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY;
private static final int HOURS_PER_SHIFT = 8;

double pay(double hoursWorked, double payRate) {
double basePay = hoursWorked * payRate;
double overtimePay;
switch (this) {
case SATURDAY:
case SUNDAY:
overtimePay = hoursWorked * payRate / 2;
default:
overtimePay = hoursWorked <= HOURS_PER_SHIFT ?
0 : (hoursWorked - HOURS_PER_SHIFT) * payRate / 2;
break;
}
return basePay + overtimePay;
}
}

不可否认,这段代码十分简洁,但是从维护的角度来看,它非常危险。假设将一个元素添加到该枚举中,或许是一个表示假期天数的特殊值,但是忘记给 switch 语句添加相应的 case。程序依然可以编译,但 pay 方法会悄悄的将假期工资计算成与正常工作日的相同。

为了利用特定于常量的方法实现安全的执行工资计算,你可能必须重复计算每个常量的加班工资,或者将计算移到两个辅助方法中(一个用来计算工作日,一个用来计算双休日),并从每个常量调用相应的辅助方法。这任何一种方法都会产生相当数量的样板代码,结果降低了可读性,并增加了出错的几率。

通过用计算工作日加班工资的具体方法代替 PayrollDay 中抽象的 overtimePay 方法,可以减少样板代码。这样,就只有双休日必须覆盖该方法了。但是这样也有着与 switch 语句一样的不足:如果又增加了一天而没有覆盖 overtimePay 方法,就会悄悄的延续工作日的计算。

你真正想要的就出每当添加一个枚举常量时,就强制选择一种加班报酬策略。幸运的是,有一种很好的办法可以实现这一点。这种想法就是将加班工资计算移到一个私有的嵌套枚举中,将这个策略枚举的实例传到 PayrollDay 枚举的构造器中。之后 PayrollDay 枚举将加班工资计算委托给策略枚举,PayrollDay 中就不需要switch语句或特定与常量的方法实现了。这种模式虽然没有 switch 语句那么简洁,但是更安全,也更加灵活。
代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41

enum PayrollDay {
MONDAY(PayType.WEEKDAY), TUESDAY(PayType.WEEKDAY),
WEDNESDAY(PayType.WEEKDAY), THURSDAY(PayType.WEEKDAY),
FRIDAY(PayType.WEEKDAY), SATURDAY(PayType.WEEKEND),
SUNDAY(PayType.WEEKEND);

private final PayType payType;

PayrollDay(PayType payType) {
this.payType = payType;
}

double pay(double hoursWorked, double payRate) {
double basePay = hoursWorked * payRate;
return basePay + payType.overtimePay(hoursWorked, payRate);
}

private enum PayType {
WEEKDAY {
double overtimePay(double hours, double payRate) {
return hours <= HOURS_PER_SHIFT ?
0 : (hours - HOURS_PER_SHIFT) * payRate / 2;
}
},
WEEKEND {
double overtimePay(double hours, double payRate) {
return hours * payRate / 2;
}
};

private static final int HOURS_PER_SHIFT = 8;

abstract double overtimePay(double hours, double payRate);

double pay(double hoursWorked, double payRate) {
double basePay = hoursWorked * payRate;
return basePay + overtimePay(hoursWorked, payRate);
}
}
}

总而言之, 如果多个枚举常量同时共享相同的行为,则考虑策略枚举

————摘自《Effective Java》