167
136

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

ValueObjectという考え方

Last updated at Posted at 2019-10-25

以前、DDD(ドメイン駆動開発)を経験した流れでいくつかのことを学びました
その中でDDDの神髄を垣間見たのでかいつまんで紹介できればと思います

記事のターゲット

DDDを学び始めた人
値オブジェクト・ValueObjectとはなにか、その片鱗を知りたい人

Value Objectとは

値オブジェクトとしてエリック本(青本)では紹介していますね

Value Objectの特徴

特徴として以下のような内容があります

  • 一意性を持たない
  • 計測/定量化/説明を責務とする
  • イミュータブルオブジェクト
  • 交換可能
  • ふるまいに副作用がない

一意性を持たない

オブジェクト毎に hogehoge_id のような一意性を表現するプロパティを含まず、一意性がない特徴です
逆にIDを持つようなオブジェクトは「Entity」といいます

この特徴の意味するところはオブジェクトをプリミティブライクに扱えることだと考えられます
これについては後述します

計測/定量化/説明を責務とする

小難しく書いていますが、例として「商品価格」「名称」といった
本来コードでは intstring といったプリミティブ型で表現していた内容を取り扱うということです

ここで「オブジェクトをプリミティブライクに扱える」意味が出てきます
具体的には以下のように扱うイメージです

class ProductPrice
  def initialize(price)
    @price = price
  end
end

class FullName
  def initialize(fullname)
    @fullname = fullname
  end
end

オブジェクト化することになんの意味があるか?と思われるかもしれないですが、後に大きな意味を持つようになります

イミュータブルオブジェクト

不変性とも言います
オブジェクトが生成された時点でオブジェクトの値が変わることを禁止しています

p = new ProductPrice(100)

# p.price = 2000 # これはダメ!!
p = new ProductPrice(2000) # 値を変える場合はオブジェクトの再生成で変更します

イミュータブルオブジェクトの利点は「オブジェクトの安全性」「スレッドセーフ」などいろいろありますが
DDDではEntityがValueObjectを多く抱えたりするので
その際にオブジェクトの状態を予想しやすくするのが重要だと捉えています

交換可能

イミュータブルの説明コードにあったように、値を変更する際にオブジェクトを交換することで実現しますが
その操作が可能である特徴です

一意性がないが故に交換可能であると言いかえることもできます

ふるまいに副作用がない

副作用とは「操作により状態が変化すること」を指しています
ValueObjectはイミュータブルである必要があるのでふるまいにより値を変更してはいけません

class ProductPrice
  attr_reader :price

  def initialize(price)
    @price = price
  end

  def add_price(add)
    # @price = price + add # ダメ!ぜったい!
    @price + add
  end
end

これも安全性の観点で非常に大事な特徴です
クラス利用者はメソッドを呼ぶときに変な気苦労をする必要がありません


つらつらと特徴について説明しましたが、あくまでValueObjectの規約みたいなもので
その価値についてはまだ触れていませんでした

Value Objectの利点

あえてプリミティブ型を使わずにオブジェクトでやる理由がほしいですね
誤解を恐れずにいうと値の持つビジネスルール(仕様)を豊かに表現できることが最大のメリットです

先ほどの例でメリットをみてみましょう

class ProductPrice
  attr_reader :price

  def initialize(price)
    @price = price
  end

  def add_price(add)
     @price = price + add # ダメ!ぜったい!
    @price + add
  end
end

これだけではただの使いにくい値ですが、表示や複雑な計算をするようになると一変します

class ProductPrice
  attr_reader :price

  def initialize(price)
    @price = price
  end

  def add_price(add)
    # @price = price + add # ダメ!ぜったい!
    @price + add
  end

  # 税を含めた価格を取得する
  def tax_included_price(tax=0.08)
    @price * (1 + tax)
  end

  # 価格3桁毎にカンマを入れて表示用にする
  def display_text
    @price.to_s.reverse.gsub(/(\d{3})(?=\d)/, '\1,').reverse
  end
end

表現力が増してただの値ではどうしようもないところもカバーできるようになりました
上記は単純なプロパティのみですが、実務では更に複雑なふるまいも増えてきますのでより効果的ですね

更にTaxとかもValueObjectにすることで税部分が複雑になっても切り分けて扱ったりできるので広がりも大きいです


なかなかイメージしにくいですが、ValueObjectは実践できればコードの見通しが良くなるので
知らなかった方は意識してコードを書いてみてはいかがでしょう?
(かくいう私もまだまだ実践できていないので精進します…)

その他

軽量DDDでもやったほうがいい理由、けどやらない理由
レイヤードアーキテクチャの視点
Serviceクラスの意義と勘所
集約で境界を正しく表現する意味

167
136
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
167
136

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?