概要
原著者の許諾を得て翻訳・公開いたします。
- 英語記事: Consider Value Objects - Andy Croll
- 原文公開日: 2019/07/28
- 著者: Andy Croll
Rails: Value Objectを検討してみよう(翻訳)
ふと気がつくと、アプリケーションで同じようなコンセプトのビューヘルパーをいくつも作ってしまっていることがよくあります(複雑な計算メソッドや、1つ以上の値をいくつものメソッドに渡すなど)。
こんなときは、Active Recordモデルより小規模なシンプルなオブジェクトでその正体を明らかにしてみましょう。
今回の場合は、機能をリファクタリングして、Martin Fowlerが提唱する「Value Object」にすることを検討します。
(Value Objectとは)通貨や日付のrangeのようなシンプルで小さなオブジェクトであり、(オブジェクトの)同一性に頼らずに等価性をチェックする。
martinfowler.comより
次のように書くのではなく
普通にヘルパーを書く。
def css_color(r, g, b)
"##{r.to_s(16)}#{g.to_s(16)}#{b.to_s(16)}"
end
次のように書く
アプリケーションで使われているコンセプトを正しく捉える「Value Object」に切り出す。
class RGBColor
def initialize(red, green, blue)
@red = [[0, red].max], 255].min
@green = [[0, green].max], 255].min
@blue = [[0, blue].max], 255].min
end
def to_hex
[@red, @green, @blue].each_with_object("") do |part, to_hex|
to_hex << part.to_s(16)
end
end
end
そして以下をヘルパーに書く
def css_color(r, g, b)
"##{RGBColor.new(r, g, b).to_hex}"
end
そうする理由
アプリケーションで使われているコンセプトがデータベースに保存されないからといって、それをアプリケーションのオブジェクトにすべきということにはなりません。
リファクタリングしてValue Objectにすればコンセプトを表現するコードを分離でき、さまざまなメリットが得られます。「関心の分離(separation of concerns)」という言葉をご存じの方もいることでしょう。このコンセプトの振る舞いのテストも分離されるので、網羅的かつ効率のよいテストを書けるようになります。
コードの整理が進むことで理解もはかどります。このコードとコンセプトは再利用しやすく、オブジェクトの機能を拡張するための格好の場所が手に入ります。
そうしない理由があるとすれば
Value Objectの導入は時期尚早の場合も考えられます。コンセプトをValue Objectに切り出すタイミングを見い出すのは簡単ではありません。Value Objectの導入が早すぎると、コードが無駄に複雑になって混乱を招く可能性もあります。逆に導入が遅れれば、ぐちゃぐちゃのコードを書くはめになります。
上の例で書いたようなクラス定義による方法以外に、Struct
、場合によってはOpenStruct
を使う手もありますが、これらのオブジェクトはミュータブルであり、プロパティを変更できてしまいますので、コードがシンプルになるどころか余計複雑になることもあります。