Tbpgr Blog

Ruby プログラマ tbpgr(てぃーびー) のブログ

書籍 Effective Java | 戦略を表現するために関数オブジェクトを利用する

パンくず

Effective Java
戦略を表現するために関数オブジェクトを利用する

概要

戦略を表現するために関数オブジェクトを利用する

詳細

関数オブジェクトを利用することで状況に合わせて実行する処理を切り替えることが可能です。
これは、デザインパターンのStrategyパターンにあたります。
Javaのライブラリの中ではComparatorが良い例です。
Comparatorの例

関数オブジェクト=多言語の関数ポインタやラムダ式と同等のものです。

サンプル仕様

入力文字を以下の手順でバリデーションチェックします。
※チェック対象は名前と年齢を持つデータクラス。

・名前がnullかチェックする
・名前が空文字かチェックする
・名前が田中かチェックする
・年齢がnullかチェックする
・年齢がマイナスかチェックする

チェックに引っかかった場合は、それぞれのエラー内容に応じたメッセージを設定した
Exceptionをthrowする。(サンプルなので例外設計は適当)

ベタ書き版と関数オブジェクトを利用したバージョンを例示します。

共通コード

ValidationIntf

package effective.creation.chapter4;

/**
 * バリデーション
 * @author tbpgr
 *
 */
public interface ValidatorIntf {
  /**
   * バリデーションを実施する。
   *
   * @throws Exception 例外
   */
  void validate() throws Exception;
}

テスト実行用クラス

package effective.creation.chapter4;

import java.util.Arrays;
import java.util.List;

public class Sample21 {
  public static void main(String[] args) {
    executeSample21_1();
    System.out.println("-----------------------------------");
    executeSample21_2();
  }

  private static void executeSample21_2() {
    List<ValidatorIntf> sample21_2s = Arrays.<ValidatorIntf> asList(new Sample21_2Validator("suzuki", 24),
        new Sample21_2Validator(null, 24), new Sample21_2Validator("", 24), new Sample21_2Validator("tanaka", 24),
        new Sample21_2Validator("suzuki", null), new Sample21_2Validator("suzuki", -1));

    executeValidatos(sample21_2s);
  }

  private static void executeSample21_1() {
    List<ValidatorIntf> sample21_1s = Arrays.<ValidatorIntf> asList(new Sample21_1Validator("suzuki", 24),
        new Sample21_1Validator(null, 24), new Sample21_1Validator("", 24), new Sample21_1Validator("tanaka", 24),
        new Sample21_1Validator("suzuki", null), new Sample21_1Validator("suzuki", -1));

    executeValidatos(sample21_1s);
  }

  private static void executeValidatos(List<ValidatorIntf> sample21_2s) {
    for (ValidatorIntf sample21_2 : sample21_2s) {
      try {
        sample21_2.validate();
        System.out.println("正常終了");
      } catch (Exception e) {
        System.out.println(e.getMessage());
      }
    }
  }
}

ベタ書き版サンプルコード

Sample21_1Validator(バリデーション用のクラス)

package effective.creation.chapter4;

public class Sample21_1Validator implements ValidatorIntf {
  private static final String TANAKA = "tanaka";
  private static final String TANAKA_NAME = "田中は禁止";
  private static final String EMPTY_NAME = "名前が空文字です";
  private static final String NULL_NAME = "名前がnull";
  private static final String NULL_AGE = "年齢がnull";
  private static final String MINUS_AGE = "年齢がマイナス";
  private String name;
  private Integer age;

  /**
   * コンストラクタにて名前、年齢を初期化する。
   *
   * @param name 名前
   * @param age 年齢
   */
  public Sample21_1Validator(String name, Integer age) {
    super();
    this.name = name;
    this.age = age;
  }

  @Override
  public void validate() throws Exception {
    if (name == null) {
      throw new Exception(NULL_NAME);
    }
    if (name.isEmpty()) {
      throw new Exception(EMPTY_NAME);
    }
    if (name.equals(TANAKA)) {
      throw new Exception(TANAKA_NAME);
    }
    if (age == null) {
      throw new Exception(NULL_AGE);
    }
    if (age < 0) {
      throw new Exception(MINUS_AGE);
    }
  }
}

関数オブジェクト利用版サンプルコード

ValidationChecker(詳細バリデーション用インターフェース)

package effective.creation.chapter4;

/**
 * Validationの個別チェッカー
 *
 * @author tbpgr
 *
 */
public interface ValidationChecker {
  /**
   * 正当性のチェックを行う。
   *
   * @return 正当な場合true、委譲な場合false
   */
  boolean isValid();
}

NullValidationChecker(Null用バリデーター)

package effective.creation.chapter4;

public class NullValidationChecker implements ValidationChecker {
  private Object target;

  /**
   * 対象を指定して初期化。
   *
   * @param target 対象
   */
  public NullValidationChecker(Object target) {
    super();
    this.target = target;
  }

  @Override
  public boolean isValid() {
    return !(target == null);
  }

}

EmptyValidationChecker(空文字用バリデーター)

package effective.creation.chapter4;

public class EmptyValidationChecker implements ValidationChecker {
  private String target;

  /**
   * 対象を指定して初期化。
   *
   * @param target 対象
   */
  public EmptyValidationChecker(String target) {
    super();
    this.target = target;
  }

  @Override
  public boolean isValid() {
    return !(target.isEmpty());
  }
}

IsTanakaValidationChecker(田中封殺用バリデーター)

package effective.creation.chapter4;

public class IsTanakaValidationChecker implements ValidationChecker {
  private static final String TANAKA = "tanaka";
  private String target;

  /**
   * 対象を指定して初期化。
   *
   * @param target 対象
   */
  public IsTanakaValidationChecker(String target) {
    super();
    this.target = target;
  }

  @Override
  public boolean isValid() {
    return !(target.equals(TANAKA));
  }
}

IsMinusValidationChecker(マイナス用バリデーター)

package effective.creation.chapter4;

public class IsMinusValidationChecker implements ValidationChecker {
  private Integer target;

  /**
   * 対象を指定して初期化。
   *
   * @param target 対象
   */
  public IsMinusValidationChecker(Integer target) {
    super();
    this.target = target;
  }

  @Override
  public boolean isValid() {
    return !(target < 0);
  }
}

Sample21_2Validator(バリデーション用のクラス)

package effective.creation.chapter4;

import java.util.Arrays;
import java.util.List;

public class Sample21_2Validator implements ValidatorIntf {
  private static final String TANAKA_NAME = "田中は禁止";
  private static final String EMPTY_NAME = "名前が空文字です";
  private static final String NULL_NAME = "名前がnull";
  private static final String NULL_AGE = "年齢がnull";
  private static final String MINUS_AGE = "年齢がマイナス";
  private String name;
  private Integer age;

  /**
   * コンストラクタにて名前、年齢を初期化する。
   *
   * @param name 名前
   * @param age 年齢
   */
  public Sample21_2Validator(String name, Integer age) {
    super();
    this.name = name;
    this.age = age;
  }

  @Override
  public void validate() throws Exception {
    List<String> errorMessages = Arrays.asList(NULL_NAME, EMPTY_NAME, TANAKA_NAME, NULL_AGE, MINUS_AGE);
    List<ValidationChecker> checkers = Arrays.<ValidationChecker> asList(new NullValidationChecker(name),
        new EmptyValidationChecker(name), new IsTanakaValidationChecker(name), new NullValidationChecker(age),
        new IsMinusValidationChecker(age));

    executeValidators(errorMessages, checkers);
  }

  private void executeValidators(List<String> errorMessages, List<ValidationChecker> checkers) throws Exception {
    for (int i = 0; i < errorMessages.size(); i++) {
      if (!checkers.get(i).isValid()) {
        throw new Exception(errorMessages.get(i));
      }
    }
  }
}

出力

正常終了
名前がnull
名前が空文字です
田中は禁止
年齢がnull
年齢がマイナス
-----------------------------------
正常終了
名前がnull
名前が空文字です
田中は禁止
年齢がnull
年齢がマイナス