Java枚举:您拥有优雅,优雅和力量,这就是我所爱!

当Java 8即将面世时,您确定您对Java 5中引入的枚举很了解吗? Java枚举仍然被低估了,很可惜,因为它们比您想象的要有用,它们不仅仅用于通常的枚举常量!

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!
  @Override
  public boolean beats(Gesture other) {
   return other == SCISSORS;
  }
 },
 PAPER, SCISSORS;

 // we can implement with the integer representation
 public 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() {
  @Override
  public 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
  @Override
  public boolean mustNotify(String eventCity, String userCity) {
   return eventCity.equalsIgnoreCase(userCity);
  }
 },

 /** I don't care, I don't want to know */
 NEVER() {
  @Override
  public boolean mustNotify(String eventCity, String userCity) {
   return false;
  }
 };

 // no default behavior
 public 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专用实现实现的好处: EnumMapEnumSet

这些集合具有相同的接口,并且行为与常规集合类似,但是在内部,它们将枚举的整数性质用作优化。 简而言之,您将旧的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 implementation
 public boolean isControlOriented() {
  return ordinal() < AGREE.ordinal();
 }

 // EnumSet is a Set implementation that benefits from the integer-like
 // nature of the enums
 public static Set DELEGATION_LEVELS = EnumSet.range(ADVISE, DELEGATE);

 // enums are comparable hence the usual benefits
 public 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或整数序列化,单例性和非常有效的集合,所有这些都只需很少的代码即可。

(图片来自sys-con.com – Jim Barnabee文章)»]
多态的力量

枚举多态性非常方便,而且我从不对枚举使用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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值