Value Objectパターンの本当に重要なポイントは「不変オブジェクト」ではなく「普通ならプリミティブ型を使ってしまうような場面でもあえてユーザー定義クラスを使う」という点にあるのではないか?


DDD(ドメイン駆動設計)のValue Objectパターンは「不変オブジェクト」であるということが強調されているが、ドメインモデリングの観点からみたら、むしろプリミティブ型を使いたくなる場面であえてユーザー定義クラスを使うということこそが本当に重要な部分なのではないか、と思ったので、そのことを書いてみる。

例題

100円の商品を三つと200円の商品を一つ買った場合の「税込みの合計金額」を計算するコードを、Value Objectパターンを使った場合と使わなかった場合で両方Javaで書いて見比べてみる。

Value Objectパターンを使わなかった場合

class Main {

    public static final double TAX_RATE = 0.08;

    public static double withTax(double price) {
        return price * (1.0 + TAX_RATE);
    }

    public static void main(String[] args) {
        double price1 = 100.0;

        int count1 = 3;

        double price2 = 200.0;

        double totalPrice = price1 * count1 + price2;

        double priceWithTax = withTax(totalPrice);

        System.out.println(priceWithTax);
    }
}

Value Objectパターンを使った場合

class Count {
    private final int rawCount;

    Count(int rawCount) {
        this.rawCount = rawCount;
    }

    public int getRawCount() {
        return this.rawCount;
    }
}

class Price {
    private final double rawPrice;

    Price(double rawPrice) {
        this.rawPrice = rawPrice;
    }

    Price plus(Price price) {
        return new Price(this.rawPrice + price.rawPrice);
    }

    Price mult(Count count) {
        return new Price(this.rawPrice * count.getRawCount());
    }

    PriceWithTax withTax() {
        return new PriceWithTax(this.rawPrice);
    }
}

class PriceWithTax {
    private static final double TAX_RATE = 0.08;

    private final double rawPriceWithTax;

    PriceWithTax(double rawPrice) {
        this.rawPriceWithTax = rawPrice * (1 + TAX_RATE);
    }

    public double getRawPriceWithTax() {
        return this.rawPriceWithTax;
    }
}


class Main {
    public static void main(String[] args) {
        Price price1 = new Price(100.0);

        Count count1 = new Count(3);

        Price price2 = new Price(200.0);

        Price totalPrice = price1.mult(count1).plus(price2);

        PriceWithTax priceWithTax = totalPrice.withTax();

        System.out.println(priceWithTax.getRawPriceWithTax());
    }
}

Value Objectパターンを使うことで、どう変わるか?

Value Objectを使っている場合、価格同士は足し合わせることしか出来ない。これが単なるdouble型であれば価格同士で引くことも割ることも掛けることも出来てしまう。
PriceからPriceWithTaxへの変換はあるが、逆の変換は無いため、価格に消費税を加えることは1回しか出来ない。
このような形で、ドメインの中で「価格」という数値に対して許される操作が、型として顕在化される。単なるdouble型では、ドメイン内でその値に対してどういう操作が意味を持つ(許されている)のかはわからない(例えば、価格の値(double型)から価格の値(double型)を引く、という操作は許されるのか?)。しかしユーザー定義クラスであれば「価格」という型に対して許される操作がメソッドとして明示的に全て記述されているのでコードの可読性が高まる。
もちろん、不変オブジェクトであるという性質もコードの可読性の向上に貢献するのだろうが、ドメインモデリングにとって本当に重要なのは普通ならdouble型のような「プリミティブ型」を使ってしまうような場面でもあえて「ユーザー定義クラス」を使うようにする(そのことによって、ドメイン内でその型に対して許される操作がメソッドとして明示的に全て記述されることになる)という部分なのではなかろうか。
まぁ、DDDのValue Objectパターンの利点は、もう色んな本やサイトで散々議論され尽くしている話題かもしれないけれど、私は最近になってようやく重い腰を上げてDDDを勉強し始めたので(笑)、とりあえず理解出来たところをコード化してみました。
ただしコードを見比べてもらえれば解るように、Value Objectパターンを使ったバージョンのほうが明らかに長い。これは一つ一つユーザー定義クラスを定義する手間がかかるため仕方がない。なのでいちいち「ユーザー定義クラスを定義する」というコストに見合うほどにコードの可読性やメンテナンス性が重要視されるような局面に使用するのが良いのだと思う。