LoginSignup
0
0

More than 5 years have passed since last update.

Item 38: Emulate extensible enums with interfaces

Posted at

38.拡張可能なenumをインターフェースで模倣すべし

たいていの場合、列挙型を拡張するような考えはよくないことであるが、operation code を書くときだけは切実に必要となる。

これをenumを使って実現する良い方法がある。enumはインターフェースを実装できるので、例えば以下のようにできる。

// 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型は拡張不可であるが、インターフェースは拡張可能であり、オペレーションのAPIとして使われるのはインターフェースである。このインターフェースを使用して、以下のように新しくoperation codeを定義することもできる。

// 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;
    }
}

基盤となるenumのインスタンスが期待されている場所であれば、拡張enumのインスタンスは使用できる。それだけでなく、基盤enumの全要素を渡すような場面でも、代わりに拡張enumの全要素を渡すことができる。以下のソースコードでは、引数に取った2つの値のExtendedOperation による全演算の結果を表示する。

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));

}

このコードにおいては、testメソッドの第一引数にExtendedOperation.class が取られている。これは境界付きの型トークン(Item33)である。
<T extends Enum<T> & Operation> とあるが、これによってtestの第一引数に与えられるクラスは、enumであり、かつ、Operation のサブクラスであることが保証される。
境界付きワイルドカード型(Item31)を使って、以下のように記述することもできる。

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%s", x, op, y, op.apply(x, y));
    }
}

このコードは少し簡潔で柔軟性が増しているが、EnumSet (Item36)とEnumMap (Item37)の使用はあきらめなければならない。
上記2つのコードは、引数に4 2 を与えたら、以下のように出力する。

4.000000 ^ 2.000000 = 16.000000
4.000000 % 2.000000 = 0.000000

この拡張enumのマイナーな欠点は、オペレーションに紐づくシンボルを保存するコードと、検索するコードはBasicOperationExtendedOperation の双方で保持しなければならない点である。あまりに多くの共通機能が出てきた場合には、ヘルパークラスなどを用意して、コードの重複を除く必要がある。

この章で語られたパターンはjava.nio.file.LinkOption でも使われている。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0