Effective Java 第三版——34. 使用枚举类型替代整型常量 (4)

另请注意,fromString方法返回一个Optional<String>。 这允许该方法指示传入的字符串不代表有效的操作,并且强制客户端面对这种可能性(条目 55)。

特定于常量的方法实现的一个缺点是它们使得难以在枚举常量之间共享代码。 例如,考虑一个代表工资包中的工作天数的枚举。 该枚举有一个方法,根据工人的基本工资(每小时)和当天工作的分钟数计算当天工人的工资。 在五个工作日内,任何超过正常工作时间的工作都会产生加班费; 在两个周末的日子里,所有工作都会产生加班费。 使用switch语句,通过将多个case标签应用于两个代码片段中的每一个,可以轻松完成此计算:

// Enum that switches on its value to share code - questionable enum PayrollDay { MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY; private static final int MINS_PER_SHIFT = 8 * 60; int pay(int minutesWorked, int payRate) { int basePay = minutesWorked * payRate; int overtimePay; switch(this) { case SATURDAY: case SUNDAY: // Weekend overtimePay = basePay / 2; break; default: // Weekday overtimePay = minutesWorked <= MINS_PER_SHIFT ? 0 : (minutesWorked - MINS_PER_SHIFT) * payRate / 2; } return basePay + overtimePay; } }

这段代码无可否认是简洁的,但从维护的角度来看是危险的。 假设你给枚举添加了一个元素,可能是一个特殊的值来表示一个假期,但忘记在switch语句中添加一个相应的case条件。 该程序仍然会编译,但付费方法会默默地为工作日支付相同数量的休假日,与普通工作日相同。

要使用特定于常量的方法实现安全地执行工资计算,必须为每个常量重复加班工资计算,或将计算移至两个辅助方法,一个用于工作日,另一个用于周末,并调用适当的辅助方法来自每个常量。 这两种方法都会产生相当数量的样板代码,大大降低了可读性并增加了出错机会。

通过使用执行加班计算的具体方法替换PayrollDay上的抽象overtimePay方法,可以减少样板。 那么只有周末的日子必须重写该方法。 但是,这与switch语句具有相同的缺点:如果在不重写overtimePay方法的情况下添加另一天,则会默默继承周日计算方式。

你真正想要的是每次添加枚举常量时被迫选择加班费策略。 幸运的是,有一个很好的方法来实现这一点。 这个想法是将加班费计算移入私有嵌套枚举中,并将此策略枚举的实例传递给PayrollDay枚举的构造方法。 然后,PayrollDay枚举将加班工资计算委托给策略枚举,从而无需在PayrollDay中实现switch语句或特定于常量的方法实现。 虽然这种模式不如switch语句简洁,但它更安全,更灵活:

// The strategy enum pattern enum PayrollDay { MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY(PayType.WEEKEND), SUNDAY(PayType.WEEKEND); private final PayType payType; PayrollDay(PayType payType) { this.payType = payType; } PayrollDay() { this(PayType.WEEKDAY); } // Default int pay(int minutesWorked, int payRate) { return payType.pay(minutesWorked, payRate); } // The strategy enum type private enum PayType { WEEKDAY { int overtimePay(int minsWorked, int payRate) { return minsWorked <= MINS_PER_SHIFT ? 0 : (minsWorked - MINS_PER_SHIFT) * payRate / 2; } }, WEEKEND { int overtimePay(int minsWorked, int payRate) { return minsWorked * payRate / 2; } }; abstract int overtimePay(int mins, int payRate); private static final int MINS_PER_SHIFT = 8 * 60; int pay(int minsWorked, int payRate) { int basePay = minsWorked * payRate; return basePay + overtimePay(minsWorked, payRate); } } }

如果对枚举的switch语句不是实现常量特定行为的好选择,那么它们有什么好处呢?枚举类型的switch有利于用常量特定的行为增加枚举类型。例如,假设Operation枚举不在你的控制之下,你希望它有一个实例方法来返回每个相反的操作。你可以用以下静态方法模拟效果:

// Switch on an enum to simulate a missing method public static Operation inverse(Operation op) { switch(op) { case PLUS: return Operation.MINUS; case MINUS: return Operation.PLUS; case TIMES: return Operation.DIVIDE; case DIVIDE: return Operation.TIMES; default: throw new AssertionError("Unknown op: " + op); } }

如果某个方法不属于枚举类型,则还应该在你控制的枚举类型上使用此技术。 该方法可能需要用于某些用途,但通常不足以用于列入枚举类型。

一般而言,枚举通常在性能上与int常数相当。 枚举的一个小小的性能缺点是加载和初始化枚举类型存在空间和时间成本,但在实践中不太可能引人注意。

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/zydpss.html