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ù)接口編寫的犀被。