The HIRO Says

If you smell what The HIRO is cooking!!!

【enum】メソッドの定義(1)−constant specific method implementation

enum では、メソッドの定義方法にもいくつかテクニックがあります。
今回は、そのうちの一つ、constant specific method implementation について説明します。


前提−四則演算を表す enum を定義する

加減乗除の四則演算を表す、ArithmeticOperation という enum を定義するとしましょう。
まず定数のみを定義すると、次のようになります。

public enum ArithmeticOperation {

    // 加算
    PLUS,
    // 減算
    MINUS,
    // 乗算
    TIMES,
    // 除算
    DIVIDE;

次に、計算を行う calculate メソッドをこれに追加することにしましょう。


よろしくない例:switch 句を使う方法

まず考えられる方法として、switch 句で enum を判定する方法があります。
具体的には、以下のようになります。

    public double calculate(double x, double y) {

        switch (this) {
            case PLUS :
                return x + y;
            case MINUS :
                return x - y;
            case TIMES :
                return x * y;
            case DIVIDE :
                return x / y;
        }

        // 宣言が失敗したことを表すには、AssertionError を使う。
        throw new AssertionError(
            "該当する演算子がありません:" + this);
    }
}

ちなみに、switch 句の後は事実上到達不能なのですが、
何も書かないとコンパイラが double を返せ!とエラーを出します。
(こういうことよくありますよね。)
こういう場合は、AssertionError を返してあげると良いそうです。(1.4 以降)


switch 句のよろしくない点

switch 句を使っても、ひとまず正常に動作させることはできます。
ですがこの方法では、新しい定数を追加する際にバグを作りこんでしまう可能性があります


例えば、剰余算(%)を新たに追加するとしましょう。
まず、定数を以下のように追加するのは分かると思います。

public enum ArithmeticOperation {
    PLUS,
    MINUS,
    TIMES,
    DIVIDE,
    MULTIPLY;

ですが、うっかり calculate での定義を忘れたらどうなるでしょう?
今の定義の場合、実行時に AssertionError が出ます。
必ず JUnit を実行している場合はまだよいでしょうが、実際に動かさないと問題に気が付かないというのは、開発経験を積んできた人にとっては怖いと思います。
できれば、コンパイル時に警告を出してくれた方が、開発者にとっては助かりますよね。


あと switch 句自体、

  1. 判定が複雑になる、
  2. テストが面倒になる、

ということで、バグの温床になりやすいと言われます。
ですので、できれば避けたいですね。


改善方法

コンパイル時に警告を出してもらい、なおかつ switch 句の使用を避けるようにするには…
デザインパターンtemplate method が、1つの解決法になります。


では、enum で template method をどうやって実現するか?
手順は次の通りです。

  1. abstract なメソッドを定義する。
  2. 各定数で、abstract メソッドを実装する。


具体的には、次のようになります。

public enum ArithmeticOperation {

    // (2)各定数で abstract メソッドを実装する。
    // 加算
    PLUS {
        public double calculate(double x, double y) {
            return x + y;
        }
    },

    // 減算
    MINUS {
        public double calculate(double x, double y) {
            return x - y;
        }
    },

    // 乗算
    TIMES {
        public double calculate(double x, double y) {
            return x * y;
        }
    },

    // 除算
    DIVIDE {
        public double calculate(double x, double y) {
            return x / y;
        }
    };


    // (1)abstract なメソッドを用意する。
    public abstract double calculate(double x, double y);
}

この手法を、enum の場合は特に constant specific method implementation(「定数毎に特化したメソッドを実装する方法」といった意味)と呼称するそうです。


NEXT

template method には、Interface を使う方法があります。
また、template method の兄弟に、stragegy があります。
これらを使うと、また enum の書き方がかわります。
これらについては、後日また説明します。