ITEM 38: 使用接口模擬擴展枚舉

ITEM 38: EMULATE EXTENSIBLE ENUMS WITH INTERFACES
??enum類型在幾乎所有方面外臂,都優(yōu)于本書第一版[Bloch01]中描述的 typesafe enum 模式。從表面上看律胀,一個例外是涉及可擴展性宋光,擴展在原始模式下是可能的,但不受語言構(gòu)造的支持炭菌。換句話說桶蝎,使用該模式,可以讓一個枚舉類型擴展另一個枚舉類型肺稀;使用語言特性就無法做到這一點挽拔。這并非偶然。在大多數(shù)情況下克握,枚舉的可擴展性是一個壞主意蕾管。令人困惑的是,擴展類型的元素是基類型的實例菩暗,反之亦然掰曾。枚舉基類型及其擴展的所有元素沒有好的實現(xiàn)方法。最后停团,可擴展性將使設計和實現(xiàn)的許多方面變得復雜旷坦。
??也就是說,對于可擴展枚舉類型至少有一個引人注目的用例佑稠,即操作代碼秒梅,也稱為操作碼。操作碼是一種枚舉類型舌胶,其元素表示某些機器上的操作番电,例如 item 34 中的操作類型,它表示簡單計算器上的函數(shù)。有時候漱办,讓API的用戶提供他們自己的操作是可取的这刷,這樣可以有效地擴展API提供的操作集。
??幸運的是娩井,有一種很好的方法可以使用枚舉類型來實現(xiàn)這種效果暇屋。基本思想是利用 enum 類型可以實現(xiàn)任意接口的事實洞辣,方法是為操作碼類型定義一個接口咐刨,并定義一個 enum (接口的標準實現(xiàn))。例如扬霜,這里有一個可擴展版本的item 34 的操作類型:

// Emulated extensible enum using an interface
public interface Operation {
  double apply(double x, double y);
}
public enum BasicOperation implements Operation { 
  PLUS("+") {public double apply(double x, double y) { return x + y; } },
  MINUS("-") {public double apply(double x, double y) { return x - y; }}, 
  TIMES("*") {public double apply(double x, double y) { return x * y; } },
  DIVIDE("/") {public double apply(double x, double y) { return x / y; }};
  private final String symbol;
  BasicOperation(String symbol) { this.symbol = symbol;}
  @Override public String toString() { return symbol;}
}

??雖然enum類型(BasicOperation)是不可擴展的定鸟,但是接口類型(Operation)是可擴展的,而且它是用于表示 api 中的操作的接口類型著瓶。您可以定義另一個實現(xiàn)此接口的 enum 類型联予,并使用該新類型的實例來替代基本類型。例如材原,假設您想定義前面顯示的操作類型的擴展沸久,包括求冪和余數(shù)操作。你所要做的就是編寫一個 enum 類型來實現(xiàn)操作接口:

// Emulated extension enum
public enum ExtendedOperation implements Operation { 
  EXP("^") {public double apply(double x, double y) { return Math.pow(x, y);} },
  REMAINDER("%") {public double apply(double x, double y) {return x % y; }};
  private final String symbol;
  ExtendedOperation(String symbol) { this.symbol = symbol;}
  @Override public String toString() { return symbol;}
}

??現(xiàn)在余蟹,您可以在任何可以使用基本操作的地方使用新操作卷胯,前提是編寫 api 是為了獲取接口類型(操作),而不是實現(xiàn)(BasicOperation)威酒。注意窑睁,您不必像在具有特定于實例的方法實現(xiàn)的非擴展枚舉中那樣在枚舉中聲明抽象 apply 方法(P 162)。這是因為抽象方法(apply)是接口(Operation)的成員葵孤。
??不僅可以在任何需要“基本枚舉”的地方傳遞“擴展枚舉”的單個實例担钮,而且還可以傳遞整個擴展枚舉類型,并在基類型的元素之外或之外使用它的元素佛呻。例如,在 P163 有一個測試程序的版本病线,它執(zhí)行前面定義的所有擴展操作:

public static void main(String[] args) { 
  double x = Double.parseDouble(args[0]); 
  double y = Double.parseDouble(args[1]); 
  test(ExtendedOperation.class, x, y);
}
private static <T extends Enum<T> & Operation> void test( Class<T> opEnumType, double x, double y) {
  for (Operation op : opEnumType.getEnumConstants()) 
    System.out.printf("%f %s %f = %f%n",x, op, y, op.apply(x, y));
}

??注意吓著,擴展操作類型(ExtendedOperation.class)的類文本從 main 傳遞到 test,以描述擴展操作集送挑。類文字用作有界類型令牌(item 33)绑莺。opEnumType 參數(shù)的復雜聲明( <T extends Enum<T> & Operation> Class<T>)確保類對象同時表示 Enum 和操作的子類型,這正是遍歷元素并執(zhí)行與每個元素關聯(lián)的操作所需要的惕耕。
??第二個替代方法是傳遞一個 Collection<? extends Operation> 纺裁,這是一個有界通配符類型(Item 31),而不是傳遞一個類對象:

public static void main(String[] args) {
  double x = Double.parseDouble(args[0]);
  double y = Double.parseDouble(args[1]); 
  test(Arrays.asList(ExtendedOperation.values()), x, y);
}
private static void test(Collection<? extends Operation> opSet, double x, double y) { 
  for (Operation op : opSet)
    System.out.printf("%f %s %f = %f%n", x, op, y, op.apply(x, y));
}

??生成的代碼稍微簡單些,測試方法也更加靈活:它允許調(diào)用者組合來自多個實現(xiàn)類型的操作欺缘。另一方面栋豫,您放棄了在指定操作上使用 EnumSet (item 36項)和 EnumMap(item 37)的功能。
當運行命令行參數(shù)4和2時谚殊,前面顯示的兩個程序都將產(chǎn)生這個輸出:

4.000000 ^ 2.000000 = 16.000000
4.000000 % 2.000000 = 0.000000

??使用接口來模擬可擴展枚舉的一個小缺點是實現(xiàn)不能從一種枚舉類型繼承到另一種枚舉類型丧鸯。如果實現(xiàn)代碼不依賴于任何狀態(tài),則可以使用缺省實現(xiàn)(item 20)將其放置在接口中嫩絮。在我們的操作示例中丛肢,存儲和檢索與操作關聯(lián)的符號的邏輯必須在 BasicOperation 和 ExtendedOperation 中復制。在這種情況下剿干,這并不重要蜂怎,因為只有很少的代碼是重復的。如果有大量的共享功能置尔,可以將其封裝在 helper 類或靜態(tài) helper 方法中杠步,以消除代碼重復。
??此項中描述的模式用于 Java庫撰洗。例如 java.nio.file.LinkOption 實現(xiàn)了 CopyOption and OpenOption 接口篮愉。
??總之,雖然不能編寫可擴展的枚舉類型差导,但是可以通過編寫接口來模擬它试躏,以便與實現(xiàn)該接口的基本枚舉類型一起使用。這允許客戶端編寫自己的枚舉(或其他類型)來實現(xiàn)接口设褐。然后颠蕴,只要可以使用基本 enum 類型的實例,就可以使用這些類型的實例助析,如果api是根據(jù)接口編寫的犀被。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市外冀,隨后出現(xiàn)的幾起案子寡键,更是在濱河造成了極大的恐慌,老刑警劉巖雪隧,帶你破解...
    沈念sama閱讀 219,427評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件西轩,死亡現(xiàn)場離奇詭異,居然都是意外死亡脑沿,警方通過查閱死者的電腦和手機藕畔,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,551評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來庄拇,“玉大人注服,你說我怎么就攤上這事韭邓。” “怎么了溶弟?”我有些...
    開封第一講書人閱讀 165,747評論 0 356
  • 文/不壞的土叔 我叫張陵女淑,是天一觀的道長。 經(jīng)常有香客問我可很,道長诗力,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,939評論 1 295
  • 正文 為了忘掉前任我抠,我火速辦了婚禮苇本,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘菜拓。我一直安慰自己瓣窄,他們只是感情好,可當我...
    茶點故事閱讀 67,955評論 6 392
  • 文/花漫 我一把揭開白布纳鼎。 她就那樣靜靜地躺著俺夕,像睡著了一般。 火紅的嫁衣襯著肌膚如雪贱鄙。 梳的紋絲不亂的頭發(fā)上劝贸,一...
    開封第一講書人閱讀 51,737評論 1 305
  • 那天,我揣著相機與錄音逗宁,去河邊找鬼映九。 笑死,一個胖子當著我的面吹牛瞎颗,可吹牛的內(nèi)容都是我干的件甥。 我是一名探鬼主播,決...
    沈念sama閱讀 40,448評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼哼拔,長吁一口氣:“原來是場噩夢啊……” “哼引有!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起倦逐,我...
    開封第一講書人閱讀 39,352評論 0 276
  • 序言:老撾萬榮一對情侶失蹤譬正,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后檬姥,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體曾我,經(jīng)...
    沈念sama閱讀 45,834評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,992評論 3 338
  • 正文 我和宋清朗相戀三年穿铆,在試婚紗的時候發(fā)現(xiàn)自己被綠了您单。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片斋荞。...
    茶點故事閱讀 40,133評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡荞雏,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情凤优,我是刑警寧澤悦陋,帶...
    沈念sama閱讀 35,815評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站筑辨,受9級特大地震影響俺驶,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜棍辕,卻給世界環(huán)境...
    茶點故事閱讀 41,477評論 3 331
  • 文/蒙蒙 一暮现、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧楚昭,春花似錦栖袋、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,022評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至尿贫,卻和暖如春电媳,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背庆亡。 一陣腳步聲響...
    開封第一講書人閱讀 33,147評論 1 272
  • 我被黑心中介騙來泰國打工匾乓, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人身冀。 一個月前我還...
    沈念sama閱讀 48,398評論 3 373
  • 正文 我出身青樓钝尸,卻偏偏與公主長得像,于是被迫代替她去往敵國和親搂根。 傳聞我的和親對象是個殘疾皇子珍促,可洞房花燭夜當晚...
    茶點故事閱讀 45,077評論 2 355