The HIRO Says

If you smell what The HIRO is cooking!!!

【enum】メソッドの定義(3)−strategyパターンを使う方法

前回、インターフェースを使って enum のメソッドを定義する方法を説明しました。
今回は、strategy パターンを使って enum のメソッドを定義する方法を説明します。


今回の仕様

今回は、とある会社の営業月を表す BusinessMonth クラス(enum)を定義することとします。
この会社は、夏のリゾート地で営業している会社としましょう。
で、月毎に料金を以下のように設定したいとしましょう。

  1. 7月と8月は、2割増とする。
  2. 11月〜2月は、1割引とする。
  3. その他の月は、標準価格とする。(割引や割増なし)

実装例−constant specific method implementation の場合

最初に説明した、constant specific method implementation で実装してみることにしましょう。

public enum BusinessMonth {

    // 1月
    JANUARY {
        public double calculateFee(double base) {
            return base * 0.9;
        }
    },

    // 2月
    FEBRUARY {
        public double calculateFee(double base) {
            return base * 0.9;
        }
    },

    // 3月
    MARCH {
        public double calculateFee(double base) {
            return base;
        }
    },

    // 4月
    APRIL {
        public double calculateFee(double base) {
            return base;
        }
    },

    // 5月
    MAY {
        public double calculateFee(double base) {
            return base;
        }
    },

    // 6月
    JUNE {
        public double calculateFee(double base) {
            return base;
        }
    },

    // 7月
    JULY {
        public double calculateFee(double base) {
            return base * 1.2;
        }
    },

    // 8月
    AUGUST {
        public double calculateFee(double base) {
            return base * 1.2;
        }
    },

    // 9月
    SEPTEMBER {
        public double calculateFee(double base) {
            return base;
        }
    },

    // 10月
    OCTOBER {
        public double calculateFee(double base) {
            return base;
        }
    },

    // 11月
    NOVEMBER {
        public double calculateFee(double base) {
            return base * 0.9;
        }
    },

    // 12月
    DECEMBER {
        public double calculateFee(double base) {
            return base * 0.9;
        }
    };


    // 料金を算出する。
    public abstract double calculateFee(double base);
}

constant specific method implementation のよろしくないところ

今回のケースでは、料金の算出方法が同じ月がいくつかあります。
通常このようなケースでは、メソッドを共有したいと思うのがプログラマの性ではないでしょうか。


ですが、constant specific method implementation を適用すると、全定数で abstract メソッドを実装しなければいけません。
共通のヘルパーメソッドを呼び出すように定義すればロジックを共有することはできますが、それでも全定数でメソッドを実装しなければいけないことには変わりありません。


1つ1つメソッドを定義するのではなくて、定数毎に実装を簡単に切り替えられればよいのに…
こういう場合に、Strategy パターンが威力を発揮します


Strategy の適用手順

特に enum で Strategy パターンを適用する際の手順を列挙します。

  • Strategy を表す enum の定義
    • Strategy を表す enum を、インナークラスと同じ要領で定義します。
    • Strategy パターン用の abstract メソッドを定義します。
    • Strategy の個々の定数で、abstract メソッドを定義します。
  • 元の enum の修正
    • Strategy を表す enum を、インスタンス変数として定義します。
    • Strategy を表す enum を、コンストラクタで設定するようにします。
    • abstract メソッドを、Strategy を表す enum のメソッドを呼び出すように変更します。
    • 処理内容に応じて、Strategy を表す enum をセットするよう、定数を修正します。


Strategy を表す enum を定義する部分は、constant specific method implementation と同じです。


Strategy の適用例

BusinessMonth クラスに Strategy パターンを適用した例を、以下に示します。

public enum BusinessMonth {

    // (2-4)処理内容に応じて Strategy enum をセットするよう、
    // 定数を修正する。
    // 1月
    JANUARY(CalculateRate.WINTER_RATE),
    // 2月
    FEBRUARY(CalculateRate.WINTER_RATE),
    // 3月
    MARCH(CalculateRate.NORMAL_RATE),
    // 4月
    APRIL(CalculateRate.NORMAL_RATE),
    // 5月
    MAY(CalculateRate.NORMAL_RATE),
    // 6月
    JUNE(CalculateRate.NORMAL_RATE),
    // 7月
    JULY(CalculateRate.SUMMER_RATE),
    // 8月
    AUGUST(CalculateRate.SUMMER_RATE),
    // 9月
    SEPTEMBER(CalculateRate.NORMAL_RATE),
    // 10月
    OCTOBER(CalculateRate.NORMAL_RATE),
    // 11月
    NOVEMBER(CalculateRate.WINTER_RATE),
    // 12月
    DECEMBER(CalculateRate.WINTER_RATE);


    // (2-1)Strategy を表す enum を、
    // インスタンス変数として定義する。
    private CalculateRate calculateRate;


    // (2-2)Strategy を表す enum を、
    // コンストラクタで設定するようにする。
    private BusinessMonth(CalculateRate calculateRate) {
        this.calculateRate = calculateRate;
    }


    // (2-3)abstract メソッドを、
    // Strategy を表す enum のメソッドを呼び出すように変更する。
    public double calculateFee(double base) {
        return base * this.calculateRate.getRate();
    }



    // (1)Strategy を表す enum の定義
    // (1-1)Strategy を表す enum を、
    // インナークラスと同じ要領で定義する。
    // 割引率を表す enumprivate enum CalculateRate {

        // (1-3)Strategy 用のメソッドを実装する。
        // 夏
        SUMMER_RATE {
            double getRate() {
                return 1.2;
            }
        },

        // 冬
        WINTER_RATE {
            double getRate() {
                return 0.9;
            }
        },

        // 通常
        NORMAL_RATE {
            double getRate() {
                return 1;
            }
        };

        // (1-2)Strategy 用のメソッドを定義する。
        // 割引率を返す。
        abstract double getRate();
    }
}