Java枚举是多态的
Java枚举是可以包含行为甚至数据的真实类。
让我们用一种方法用枚举来代表剪刀石头布游戏。 以下是定义行为的单元测试:
@Test
public void paper_beats_rock() {assertThat(PAPER.beats(ROCK)).isTrue();assertThat(ROCK.beats(PAPER)).isFalse();
}
@Test
public void scissors_beats_paper() {assertThat(SCISSORS.beats(PAPER)).isTrue();assertThat(PAPER.beats(SCISSORS)).isFalse();
}
@Test
public void rock_beats_scissors() {assertThat(ROCK.beats(SCISSORS)).isTrue();assertThat(SCISSORS.beats(ROCK)).isFalse();
}
这是枚举的实现,它主要依赖于每个枚举常量的序数整数,例如项N + 1胜过项N。在许多情况下,枚举常量和整数之间的等效关系非常方便。
/** Enums have behavior! */
public enum Gesture {ROCK() {// Enums are polymorphic, that's really handy!@Overridepublic boolean beats(Gesture other) {return other == SCISSORS;}},PAPER, SCISSORS;// we can implement with the integer representationpublic boolean beats(Gesture other) {return ordinal() - other.ordinal() == 1;}
}
注意,在任何地方都没有一个IF语句,所有业务逻辑都由整数逻辑和多态性处理,在这里我们将覆盖ROCK情况的方法。 如果项目之间的排序不是循环的,我们可以仅使用枚举的自然排序来实现,这里的多态性有助于处理循环。
您无需任何IF语句就可以做到! 是的你可以! |
这个Java枚举也是一个完美的例子,您可以吃下蛋糕(提供带有意图公开名称的漂亮的面向对象的API),也可以吃下蛋糕(使用美好时光的简单而有效的整数逻辑实现)。
在我的上一个项目中,我使用了很多枚举来代替类:它们被保证为单例,具有顺序,哈希码,等值和序列化,并且都是内置的,而源代码中没有任何混乱。
如果您正在寻找Value Objects,并且可以用一组有限的实例代表域的一部分,那么枚举就是您所需要的! 它有点像Scala中的Sealed Case类 ,但是它完全限于在编译时定义的一组实例。 编译时实例的有限集合是一个真正的限制,但是现在有了连续交付功能 ,如果确实需要一种额外的情况,则可以等待下一个版本。
非常适合策略模式
让我们进入一个(著名的) 欧洲歌唱大赛的系统 ; 我们希望能够配置何时向用户通知(或不通知)任何新的Eurovision事件的行为。 这一点很重要。 让我们用一个枚举来做到这一点:
/** The policy on how to notify the user of any Eurovision song contest event */
public enum EurovisionNotification {/** I love Eurovision, don't want to miss it, never! */ALWAYS() {@Overridepublic boolean mustNotify(String eventCity, String userCity) {return true;}},/*** I only want to know about Eurovision if it takes place in my city, so* that I can take holidays elsewhere at the same time*/ONLY_IF_IN_MY_CITY() {// a case of flyweight pattern since we pass all the extrinsi data as// arguments instead of storing them as member data@Overridepublic boolean mustNotify(String eventCity, String userCity) {return eventCity.equalsIgnoreCase(userCity);}},/** I don't care, I don't want to know */NEVER() {@Overridepublic boolean mustNotify(String eventCity, String userCity) {return false;}};// no default behaviorpublic abstract boolean mustNotify(String eventCity, String userCity);}
并针对非平凡案例进行单元测试:ONLY_IF_IN_MY_CITY:
@Test
public void notify_users_in_Baku_only() {assertThat(ONLY_IF_IN_MY_CITY.mustNotify("Baku", "BAKU")).isTrue();assertThat(ONLY_IF_IN_MY_CITY.mustNotify("Baku", Paris")).isFalse();
}
在这里,我们定义方法abstract ,仅在每种情况下都实现它。 一种替代方法是实现默认行为,并且仅在有意义的每种情况下才覆盖默认行为 ,就像在Rock-Paper-Scissors游戏中一样。
同样,我们不需要打开枚举来选择行为,而是依靠多态。 除了依赖关系之外,您可能不需要太多的枚举。 例如,当枚举是数据传递对象(DTO)中发送给外界的消息的一部分时,您不希望枚举或其签名中的内部代码具有任何依赖性。
对于欧洲电视网的策略,使用TDD我们可以从一个简单的布尔值开始(对于ALWAYS和NEVER)。 一旦我们引入第三个策略ONLY_IF_IN_MY_CITY,它将立即被提升为枚举。 提倡基元也是本着Object Calisthenics第七条规则“ 包装所有基元 ”的精神,而枚举是将布尔值或整数与一组可能的值进行包装的理想方法。
由于策略模式通常是由配置控制的,因此来往String的内置序列化也非常方便存储设置。
完美匹配国家模式
就像策略模式一样,Java枚举非常适合于有限状态机 ,根据定义,可能状态的集合是有限的。
婴儿作为有限状态机(图片来自www.alongcamebaby.ca) |
让我们以简化为状态机的婴儿为例,并使其成为枚举:
/*** The primary baby states (simplified)*/
public enum BabyState {POOP(null), SLEEP(POOP), EAT(SLEEP), CRY(EAT);private final BabyState next;private BabyState(BabyState next) {this.next = next;}public BabyState next(boolean discomfort) {if (discomfort) {return CRY;}return next == null ? EAT : next;}
}
当然,还有一些单元测试可以驱动行为:
@Test
public void eat_then_sleep_then_poop_and_repeat() {assertThat(EAT.next(NO_DISCOMFORT)).isEqualTo(SLEEP);assertThat(SLEEP.next(NO_DISCOMFORT)).isEqualTo(POOP);assertThat(POOP.next(NO_DISCOMFORT)).isEqualTo(EAT);
}@Test
public void if_discomfort_then_cry_then_eat() {assertThat(SLEEP.next(DISCOMFORT)).isEqualTo(CRY);assertThat(CRY.next(NO_DISCOMFORT)).isEqualTo(EAT);
}
是的,我们可以引用它们之间的枚举常量,但前提条件是只能引用以前定义的常量。 在这里,我们在状态EAT-> SLEEP-> POOP-> EAT等之间有一个循环。因此,我们需要打开循环并在运行时使用解决方法将其关闭。
我们确实有一个带有CRY状态的图 ,可以从任何状态访问它。
我已经使用枚举通过简单地在每个节点中引用其元素(都带有枚举常量)来按类别表示简单树 。
枚举优化的集合
枚举还具有为其Map和Set专用实现实现的好处: EnumMap和EnumSet 。
这些集合具有相同的接口,并且行为与常规集合类似,但是在内部,它们将枚举的整数性质用作优化。 简而言之,您将旧的C样式数据结构和习惯用法(位掩码等)隐藏在优雅的界面后面。 这也说明了您不必为了效率而妥协API!
为了说明这些专用集合的用法,让我们代表Jurgen Appelo的委派扑克中的7张牌:
public enum AuthorityLevel {/** make decision as the manager */TELL,/** convince people about decision */SELL,/** get input from team before decision */CONSULT,/** make decision together with team */AGREE,/** influence decision made by the team */ADVISE,/** ask feedback after decision by team */INQUIRE,/** no influence, let team work it out */DELEGATE;
一共有7张卡,前三张卡更加面向控制,中间的卡平衡,最后三张卡则更加面向委托(我已经解释清楚了,请参阅他的书进行解释)。 在“委托扑克”中,每个玩家都为给定情况选择一张牌,并赚取与该牌价值(从1到7)一样多的积分,“最高少数民族”的玩家除外。
使用顺序值+ 1计算点数很简单。通过顺序值选择面向控制的卡也很简单,或者我们可以像下面所做的那样使用从范围构建的Set来选择面向委托的牌:
public int numberOfPoints() {return ordinal() + 1;}// It's ok to use the internal ordinal integer for the implementationpublic boolean isControlOriented() {return ordinal() < AGREE.ordinal();}// EnumSet is a Set implementation that benefits from the integer-like// nature of the enumspublic static Set DELEGATION_LEVELS = EnumSet.range(ADVISE, DELEGATE);// enums are comparable hence the usual benefitspublic static AuthorityLevel highest(List levels) {return Collections.max(levels);}
}
EnumSet提供了方便的静态工厂方法,例如range(from,to),以创建一个集合,该集合包括在我们的示例中按声明顺序从ADVISE和DELEGATE开始的每个枚举常量。
为了计算最高的少数派,我们从最高的牌开始,除了找到最大值外,别无所求,因为枚举始终是可比的,所以这很琐碎。
每当我们需要将此枚举用作Map中的键时,都应使用EnumMap,如以下测试所示:
// Using an EnumMap to represent the votes by authority level
@Test
public void votes_with_a_clear_majority() {final Map<AuthorityLevel, Integer> votes = new EnumMap(AuthorityLevel.class);votes.put(SELL, 1);votes.put(ADVISE, 3);votes.put(INQUIRE, 2);assertThat(votes.get(ADVISE)).isEqualTo(3);
}
Java枚举很好,吃掉它们!
我喜欢Java枚举:它们在域驱动设计的意义上非常适合值对象,在此意义上限制了所有可能值的集合。 在最近的项目中,我特意设法将大多数值类型表示为枚举。 免费提供许多很棒的功能,尤其是几乎没有技术噪音的情况下。 这有助于提高我在域词和技术术语之间的信噪比 。
或者当然,我确保每个枚举常量也是不可变的 ,并且免费获取了正确的等于,哈希码,toString,String或整数序列化,单例性和非常有效的集合,所有这些都只需很少的代码即可。
多态的力量 |
枚举多态性非常方便,而且我从不对枚举使用instanceof ,也几乎不需要打开枚举。
我希望Java枚举由类似的构造完成,就像Scala中的case类一样,因为当可能的值集不能被限制时。 强制任何类保持不变的方法也很好。 我问得太多了吗?
同样,<troll>甚至都不要尝试将Java枚举与C#枚举进行比较... </ troll>
参考: Java枚举:您拥有优雅,优雅和力量,这就是我所爱! 从我们的JCG合作伙伴 Cyrille Martraire在Cyrille Martraire的博客博客中获得。
翻译自: https://www.javacodegeeks.com/2012/08/java-enums-you-have-grace-elegance-and.html