第34條:用接口模擬可伸縮的枚舉

????枚舉類型是不可擴展的,但是接口類型是可擴展的。使用接口,可以模擬可伸縮的枚舉。

????就幾乎所有方面來看,枚舉類型都優越于類型安全枚舉模式。從表面上看,有一個異常與伸縮性有關,這個異??赡芴幵谠瓉淼哪J街?,卻沒有得到語言構造的支持。換句話說,使用這種模式,就有可能讓一個枚舉類型去擴展另一個枚舉類型;利用這種語言特性,則不可能這么做。這絕非偶然。枚舉的可伸縮性最后證明基本上都不是什么好點子。擴展類型的元素是基本類型的實例,基本類型的實例卻不是擴展類型的元素,這樣很混亂。

????類型安全枚舉模式:定義一個類來代表枚舉類型的單個元素,并且不提供任何公有的構造函數。相反,提供公有的靜態final域,使枚舉類型中的每一個常量都對應一個域。

public class Suit {
    private final String name;
    
    private Suit(String name){
        this.name = name;
    }
    
    public String toString(){
        return name;
    }
    
    public static final Suit CLIBS = new Suit("clubs");
    public static final Suit DIAMONDS = new Suit("diamonds");
    public static final Suit HEARTS = new Suit("hearts");
    public static final Suit SPADES = new Suit("spades");
}

????要想類型安全模式可以被擴展,只要提供一個受保護的構造函數就可以。然后其他人就可以擴展這個類,并且在子類中增加新的常量。類型安全枚舉模式可擴展變形與可序列化變形是兼容的,但是兩者組合的時候要非常小心,每個子類必須分配自己的序數,并且提供自己的readResolve方法。下邊的Operation類是可擴展的、可序列化的類型安全枚舉類(每個,枚舉類型極其定義的枚舉變量在JVMh中都是唯一的):

public abstract class Operation1 implements Serializable{
    private final transient String name;//標記為transient的變量不會被序列化
    
    protected Operation1(String name) {
        this.name = name;
    }
    public String toString(){
        return this.name;
    }
    public static Operation1 PLUS = new Operation1("+"){
        protected double eval(double x, double y) {
            return x + y;
        }
    };
    public static Operation1 MINUS = new Operation1("-"){
        protected double eval(double x, double y) {
            return x - y;
        }
    };
    public static Operation1 TIMES = new Operation1("*"){
        protected double eval(double x, double y) {
            return x * y;
        }
    };
    public static Operation1 DIVIDE = new Operation1("/"){
        protected double eval(double x, double y) {
            return x / y;
        }
    };
    protected abstract double eval(double x, double y);
    private static int nextOrdinal = 0;
    private final int ordinal = nextOrdinal++;
    private static final Operation1[] VALUES ={PLUS,MINUS,TIMES,DIVIDE};
    Object readResolve() throws ObjectStreamException{
        return VALUES[ordinal];
    }

????下面是Operation的一個子類,它增加了對數和指數的操作。他本身也是可以擴展的。

public abstract class ExtendedOperation1 extends Operation1 {
    ExtendedOperation1(String name) {
        super(name);
    }
    public static Operation1 LOG = new Operation1("log"){
        protected  double eval(double x, double y) {
            return Math.log(y)/Math.log(x);
        }
    };
    public static Operation1 EXP = new Operation1("exp"){
        protected double eval(double x, double y) {
            return x + y;
        }
    };
    
    private static int nextOrdinal = 0;
    private final int ordinal = nextOrdinal++;
    private static final Operation1[]  VALUES = {LOG,EXP};
    
    Object readResolve() throws ObjectStreamException{
        return VALUES[ordinal];
    }

????有一種很好的方法來實現可伸縮性的枚舉,以下是第30條中的Operation類型的擴展版本:

/**
 * 雖然枚舉類型是不能擴展的 , 但是可以通過接口類表示API中的操作的接口類型 . 
 * 你可以定義一個功能完全不同的枚舉類型來實現這個接口 .  
 */
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;
    }   
}

????你可以定義另外一個枚舉類型,它實現這個接口,并用這個新類型的實例代替基本類型。例如,假設你想要定義一個上述操作類型的擴展,由求冪和求余操作組成。你所要做的就是編寫一個枚舉類型,讓它實現Operation接口:

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

????注意,在枚舉中,不必像在不可擴展的枚舉中所做的那樣,利用特定于實例的方法實現來聲明抽象的apply方法。這是因為抽象的方法是接口的一部分。

????不僅可以在任何需要“基本枚舉”的地方單獨傳遞一個"擴展枚舉"的實例,而且除了那些基本類型的元素之外,還可以傳遞完整的擴展枚舉類型,并使用它的元素。通過下面這個測試程序,體驗一下上面定義過的所有擴展過的操作:

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

????第二種方法是使用Collection<? extends Operation>,這個有限的通配符類型,作為opSet參數的類型:

import java.util.Arrays;
import java.util.Collection;
public class test2 {
public static void main(String[] args) {
    double x = 2;
    double y = 4 ;
    
    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));
    }
}
}

????上面這兩段程序用命令行參數 2和4運行時,都會產生這樣的輸出:
2.000000 ^ 4.000000= 16.000000
2.000000 % 4.000000= 2.000000

????用接口模擬可伸縮枚舉有個小小的不足,即無法將實現從一個枚舉類型繼承到另一個枚舉類型。在上Operation的實例中,保存和獲取與某項操作相關聯的符號的邏輯代碼,可以復制到BasicOperation和ExtendedOperation中。在這個例子中是可以的,因為復制的代碼非常少。如果共享功能比較多,則可以將它封裝在一個輔助類或者靜態輔助方法中,來避免代碼的復制工作。

????總而言之,雖然無法編寫可擴展的枚舉類型,卻可以通過編寫接口以及實現該接口的基礎枚舉類型,對它進行模擬。這樣允許客戶端編寫自己的枚舉來實現接口。如果API是根據接口編寫的,那么在可以使用基礎枚舉類型的任何地方,也就可以使用這些枚舉。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • 枚舉類型是指由一組固定的常量組成合法值的類型,例如一年中的季節,太陽系中的行星或者一副牌中的花色。在編程語言...
    小小輝_710a閱讀 1,460評論 0 0
  • Spring Cloud為開發人員提供了快速構建分布式系統中一些常見模式的工具(例如配置管理,服務發現,斷路器,智...
    卡卡羅2017閱讀 134,973評論 19 139
  • Java 1.5發行版本新增了兩個引用類型家族:枚舉類型(Enumerate類)和注解類型(Annotation接...
    Timorous閱讀 421評論 0 0
  • { "Unterminated string literal.": "未終止的字符串文本。", "Identifi...
    一粒沙隨風飄搖閱讀 10,768評論 0 3
  • 昨天晚上我們學習完了第一幕,復習完昨天學的知識:丹麥的一個老國王死了,本該是哈姆萊特來繼承王位,結果卻是他的叔父來...
    petermeng閱讀 350評論 3 5