マニュアル: Value Object

投稿日: 更新日:

作り方

まとめ中です。

  • クラス名:
  • 型:
  • 変数名:
import java.util.Objects;
import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.HashCodeBuilder;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;

public final class {{ class_name }} {
  @NonNull private final {{ field_type }} {{ field_name }};

  public static {{ class_name }} of({{ field_type }} {{ field_name }}) {
    return new {{ class_name }}({{ field_name }});
  }

  private {{ class_name }}({{ field_type }} {{ field_name }}) {
    String checked = Objects.requireNonNull({{ field_name }}, "{{ field_name }}がnullです。");

    // TODO: 引数の中身のチェックを追加
    this.{{ field_name }} = checked;
  }

  @NonNull
  public {{ field_type }} value() {
    return this.{{ field_name }};
  }

  @Override
  public boolean equals(Object obj) {
    return EqualsBuilder.reflectionEquals(this, obj);
  }

  @Override
  public int hashCode() {
    return HashCodeBuilder.reflectionHashCode(this);
  }

  /**
   * このオブジェクトの簡潔な説明を返します。
   * ただし、この表現は明記せず、変更されることがあります。
   */
  @Override
  public String toString() {
    return ToStringBuilder.reflectionToString(this, ToStringStyle.SHORT_PREFIX_STYLE);
  }
}

ポイント

  • 値ベース・クラスも参考になりそうです。
  • クラスはfinalにする。
    • これに限らず、原則はfinalがよいです。
  • ファクトリーメソッドofを定義
    • ファクトリーメソッドを使うため、Flyweightパターンが適用可能。
    • サブクラスを返すことも可能だが、finalなのでサブクラスは存在しない。
      • Immutablesを使えば、実装とインタフェースを分離可能。
    • ofは短くて広く使われているため。
    • 引数の内容に条件がある場合はofBase64(String)のように条件をメソッド名に付ける。
    • フォームから渡されたときなど、エラー時にチェック例外を投げたい場合は、別メソッドを作る。
      • メソッド名はparseが良さそう1
  • コンストラクタ or ファクトリで引数のチェックを行う。
    • nullチェック
    • 空文字列など、使う側からしたらありえないもの
    • IllegalArgumentExceptionを投げる2
    • ofparse両方実装する場合を考えると、引数チェックはファクトリの方がいいかもしれない。
  • 必要な処理はコンストラクタで全て行っておく
    • 逆に言えば、他のメソッドでIllegalStateExceptionを投げてはいけない。
    • 本当の原因であるコンストラクタがどこで呼び出されたかが分からないため、障害解析が困難になる。
  • toString()Apache Commons Langを使ってます。
    • toString()はログに使うことがメインなので、このような実装を好んでいます。
      • ただし、ValueObjectはオブジェクトのIDには意味がないため、SHORT_PREFIX_STYLEを使います。
    • フィールドの値を返したい場合は、他のメソッドを別途定義します。
    • 処理から除外したい場合はアノテーションToStringExcludeを付けると良いみたいです。
    • Javadocコメントで、変更されることがあることを明記します。
  • equals()Apache Commons Langを使っています。まだこのコードはテストしていません。
    • 処理から除外したい場合はアノテーションEqualsExcludeを付けると良いみたいです。
  • hashCode()Apache Commons Langを使っています。まだこのコードはテストしていません。
    • 処理から除外したい場合はアノテーションHashCodeExcludeを付けると良いみたいです。

テスト

equals()のテストには、EqualsVerifierを使うと良さそうです。

処理を追加する場合

  • チェック例外を返したい(例外を呼び出し元で処理させたい)場合
    • ファクトリーメソッドに例外を付ける。
  • テストクラスは、同じパッケージ名で、クラス名の最後にTestを付けたものにする。
    • パッケージプライベートが使用できるため。
  • ゲッターは避ける(※検討中)
    • テストで必要な場合はパッケージプライベートにする。
    • 基本型でなく別のValue Objectを返せないか?
    • ゲッターではなく、処理を依頼できないか?
    • サービスがふさわしくないか?
    • メソッド名に安易にgetをつけてないか?

  1. 例: java.time.Year#parse() [return]
  2. Effective JavaではnullのときはNullPointerExceptionを投げると良いとされてますが、「引数間違い」という意図を分かりやすくするためにIllegalArgumentExceptionを好んでます。 [return]

外部サイト