Help us understand the problem. What is going on with this article?

DDDで値オブジェクトを生成するときってその場でnewで良いの?

More than 1 year has passed since last update.

疑問

エンティティなり集約ルートなりを生成する場合にはファクトリから生成するべきというのはわかったんですけど、値オブジェクトの生成についてはDDD本には記述が見つけられなかったんですよ(あったらごめんなさい)。
IDDD本では値オブジェクトのコンストラクタの実装についての記述はあったんですが使用しているコードが見つけられなくて。
なので値オブジェクトの生成に関して自分なりの考察を投稿してみます。

値オブジェクトの生成手法一覧(自分が思いつく限り)

手法 メリット デメリット
1 その場でnew(コンストラクタ使用) 単純 不変条件チェックでNGならコンストラクタで例外
2 staticファクトリーメソッド 呼び出しが楽。不変条件のチェックが出来る 依存性を注入しづらい
3 集約ルートのようにファクトリ用意 不変条件のチェックが出来る。いろいろ注入できる 一個一個の値オブジェクトに対して作成するのはつらい。使うときにファクトリの注入が必要

以下、それぞれについて記載します。

その場でnew(コンストラクタ使用)

良くない気がします。
値オブジェクトの不変条件のチェックをどこでやるかっていう話にもなるとは思うんですけど、不変条件を値オブジェクト内に閉じ込めようと思うとコンストラクタでチェックをしないといけなくなります。そこで違反していた場合、違反していることを伝えるためには例外を投げるしか方法がなくなるんですよね。(C#とか想定)
あとはファクトリメソッドパターンを適用しようとか事前条件を加えたいとか考えると柔軟性に欠ける気がします。

staticファクトリーメソッド

自分的にはベターな手法です。
事前条件や不変条件を修正しやすいですし、戻り値をboolと値オブジェクトのタプルで返すようなメソッドを用意すれば事前条件や不変条件の違反を伝える術が例外に頼らなくて済みます。
もしアプリ起動中ずっと生存していて良いのならシングルトンパターンで記述しても良いですし。

staticメソッドのデメリットとしてクラス名が変更した時全直しが必要ですけど、値オブジェクトの名前なんてそうそう変わるはずないですよね?(願望)
あとは値オブジェクトに依存性を注入する必要が出た場合、注入するためには使用元のクラスで注入してからstaticファクトリーメソッドの引数として渡さないといけないところですかね。

staticファクトリーメソッドだとDIするとき大変
// 値オブジェクト.
public class xxxValueObject
{
    private IyyyService yyyService { get; }
    public int Value { get; }

    // staticファクトリーメソッド.必要なら値チェックしてから生成.
    public static xxxValueObject Create(IyyyService _yyyService, int _value)
        => new xxxValueObject(_yyyService, _value);

    private xxxValueObject(IyyyService _yyyService, int _value)
    {
        yyyService = _yyyService;
        Value = _value;
    }

    public int ここでやらなければならないこと => yyyService.なんかやる(Value);

    // 以下、等価性担保
    ...
}

// 使用するクラス.
public class zzzModel
{
    private IyyyService yyyService { get; }

    // コンストラクタインジェクション.
    public zzzModel(IyyyService _yyyService)
    {
        yyyService = _yyyService;
    }

    // 使用する場所.
    public void hogehoge()
    {
        var vo = xxxValueObject.Create(yyyService, 10);
        // vo.ここでやらなければならないこと を呼んでなんかやる.
        // vo.ここでやらなければならないこと を使わないときでも注入必要.
        ...
    }
}

けど値オブジェクトに注入することってあるんですかね?設計を見直した方が良い気がします。

集約ルートのようにファクトリ用意

これはやりすぎな気がします。
1つの値オブジェクトにつき、ファクトリのインターフェースとファクトリの実装クラスが必要になっちゃいます。
値オブジェクトファクトリみたいにひとつにまとめるのもなんか違う気がします。(値オブジェクトのサービスロケータになってしまう)
使用するときも使用先でファクトリを注入しないと使えません。

所感

結局は場合によりけりなんでしょうけど。
他にも生成の方法やテクニックがありましたらご教示頂きたいです。

kwhrkzk
業務系に従事。WPF,PRISM,C#,F#,.NET Core,Laravel,Vue,TypeScript,DDD,CQRS,SOLIDらへんに興味有。
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away