【Java】if文による条件分岐よりもEnumを使った"設定"を-Strategy Enum


if文による条件分岐の複雑さ

ある日が過去・現在・未来なのか判定し、それに応じて処理を分けるとします。これをif文を使って実装すると以下のようなコードになります。実際、この程度ならば見通しがそこまで悪いわけではありませんが、更に細かく条件を分けようとすると、複雑な分岐を読み解く必要があります。

Main.java
public static void main(String[] args) {    
    //変数:pastの時制を判定したい
    LocalDate past =  LocalDate.of(2019, 10, 11);

    LocalDate today = LocalDate.now();  //現在は(2019-10-12)
        if(past.isBefore(today)) {
            System.out.println("Past");
        } 
        if(past.isEqual(today)) {
            System.out.println("Today");
        }
        if(past.isAfter(today)) {
            System.out.println("After");
        }
    //出力: Past
}

「if文による条件分岐」よりも「条件-処理をまとめた設定」を

Strategy Enumを使うと、条件とそれに応じた処理を1対1で記述することができます。

まず、Enum(DatePattern)のフィールドに次の関数型インターフェイスを2つ定義します
 1. 条件: Predicate
 2. 処理: Runnnable

次に、ここではEnumのクラスメソッド of()として、Factory Methodを記述します。Factory Methodは引数で受け取った日付から時制を判定し、それに応じた処理を引き当て、返り値として返却します。

このようにすると、条件と処理を設定 として記述できます。

DatePattern.java
public enum DatePattern {
    //(条件, 処理)の設定
    Past(date -> isBeforeToday(date), () -> System.out.println("Past")),
    Today(date -> isSameToday(date), () -> System.out.println("Today")),
    Future(date -> isAfterToday(date), () -> System.out.println("Future"));

    private final Predicate<LocalDate> dateValidator; //条件
    private final Runnable function; //処理

    //Enumのコンストラクタ
    DatePattern(Predicate<LocalDate> dateValidator, Runnable function) {
        this.dateValidator = dateValidator;
        this.function = function;
    }

    /* Factory Method 
     * 引数のLocal Dateの時制をフィールドで定義した条件(Predicate)で判定して、
     * 処理(Runnnable)を返却する
    */ 
    public static Runnable of(LocalDate date) {
        Optional<Runnable> pattern = Stream.of(DatePattern.values())
                .filter(e -> e.dateValidator.test(date))
                .map(e -> e.function)
                .findFirst();
        return Optional.ofNullable(pattern.get()).orElseThrow(IllegalArgumentException::new);
    }

    // 過去かどうかの判定
    private static boolean isBeforeToday(LocalDate date) {
        LocalDate today = LocalDate.now();
        return date.isAfter(today);
    }
    // 未来かどうかの判定
    private static boolean isAfterToday(LocalDate date) {
        LocalDate today = LocalDate.now();
        return date.isBefore(today);
    }
    // 現在かどうかの判定
    private static boolean isSameToday(LocalDate date) {
        LocalDate today = LocalDate.now();
        return date.equals(today);
    }    
}

Enumを使った結果、以下のようにif文を消すことができます。条件分岐が無いため、見通しを良くすることができました。また、更に細かい判定条件を追加しても、このDatePatternの呼び出し元に影響はありません。
if文による条件分岐を次々に繰り返すのではなく、 Enumに条件と処理をワンセットにして設定 する方が個人的に見通しが高く、保守性が高まると考えています。

Main.java
public static void main(String[] args) {
    LocalDate past =  LocalDate.of(2019, 10, 11);  //過去
    LocalDate today = LocalDate.of(2019, 10, 12);  //現在(2019-10-12)
    LocalDate future = LocalDate.of(2019, 10, 13); //未来

    Runnable past_function = DatePattern.of(past);
    Runnable today_function = DatePattern.of(today);
    Runnable futute_function = DatePattern.of(future);

    past_function.run();
    // 出力: Past
    today_function.run();
    // 出力: Today
    futute_function.run();
    // 出力: Future
}