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