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

[Python]dataclassesを使ってValueObjectを完全コンストラクタで生成する

はじめに

最近、無謀にもDDDの勉強を始めたのですが、Pythonの例が少ない・・・!
ということで、
・DDDに関する自分の考えを整理する
・Pythonによる記述方法の一例を提案する
という目的で記事を書いてみることにしました。
とりあえず今回は一番とっつきやすそうなValueObjectについて書いています。

ValueObjectと完全コンストラクタとは・・・

ちゃんとした説明は他の方々のとても分かりやすい記事がたくさんあるのでそちらに譲るとして、簡単に書くと、
ValueObjectは「値をintなどのプリミティブ型ではなく、期待している振る舞い、在り方をクラスを使って表現したもの」、
完全コンストラクタは「期待している振る舞い」や、逆に「期待しない振る舞いをさせない」を実現するための手法と言えます。
(私はこう理解しています・・・合ってる・・・?)

例えば、「金額」を表現するのであれば「負の値にはならない」など、表現する対象物に対して
本来あるべき振る舞いなどをクラスで定義したものがValueObjectです。
上記の「負の値にはならない」というのをコンストラクタの中で記述して、
「存在してはならない値を存在させない」ということを実現するのが完全コンストラクタです。

dataclassesでValueObjectを表現してみる

前節で金額の話をしたので、金額を例に簡単に書いてみます。

import dataclasses

@dataclasses.dataclass
class Money:
    # 詳細は省きますが、ここに挙げた変数が通常のクラスの __init__ に記述するインスタンス変数となります
    amount: int

    # __post_init__ は __init__ の後に実行される処理です。
    # 完全コンストラクタの表現をここに記述します。
    def __post__init__(self):
        if self.amount < 0:
            raise ValueError()

これで負の値を持ったMoneyオブジェクトは存在できず、
正しい値を持っていることが保証されたMoneyオブジェクトだけが存在することになります。
(他にも課すべき制限はあるかと思いますが、今回はこの程度にして話を先に進めます)

イミュータブルにする

dataclassesを使うと、クラスをイミュータブルにすることもできます。
イミュータブルにすることで再代入が不可となり、コードの安全性が向上します。
(ValueObjectは基本的にイミュータブルにするべきもののようです)
Pythonはインスタンス変数への再代入を防ぐ方法が乏しいため、重要な機能です。

import dataclasses

# frozen=Trueとするとイミュータブルになる
@dataclasses.dataclass(frozen=True)
class Money:
    amount: int

    def __post__init__(self):
        if amount < 0:
            raise ValueError()

金額の変化をどう表現するか

イミュータブルにしたことで一度インスタンス化すると、その金額の増減が発生しても値を変更できなくなりました。
では、金額の増減はどう表現するか、増減後の金額の値を持った新しいインスタンスを生成します。
メソッドは通常のクラスと同様に記述できます。

import dataclasses

@dataclasses.dataclass(frozen=True)
class Money:
    amount: int

    def __post__init__(self):
        if amount < 0:
            raise ValueError()

    def lost(self, loss):
        return Money(self.amount - loss.amount)

# 例えばこんな感じに書きます
money1 = Money(1000)
money2 = Money(100)
left_money = money1.lost(money2)

dataclassesを使うべきか

従来の記述方法でも同様のことは可能なので必須ではありません。
ただ、
・イミュータブルにできる
・コンストラクタの記述量が減らせる
・インスタンス変数の記述と完全コンストラクタにするためのロジックの記述を分離でき、見やすくなる
という点でdataclassesを使ったほうがいいかなぁという印象です。
dataclassesは他にも様々な機能があるのでValueObjectに限らず使ってみたいですね。

最後に

このように書けばPythonでもValueObjectを表現できる、ということを今回紹介させていただきました。
まだ勉強し始めたばかりのためValueObjectそのものについての説明は薄いですが、理解が深まれば
補足の記事を書いたり、エンティティなどについても記事にできればと思っています。

参考

dataclassesの用法はこちらを参考にしています。
https://docs.python.org/ja/3.7/library/dataclasses.html

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
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  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
ユーザーは見つかりませんでした