元底辺エンジニアが語る、エンジニアとしての生き様、そしてこれからの生き方

生き様104. 値オブジェクトについて考える

»

今月のテーマ「値オブジェクト」

毎月第一週目は、技術関係の話題です。
今回のテーマは、【値オブジェクト】です。

今回は「値オブジェクト」について、僕の理解をベースに、初歩的な解説や、よくある誤解について解説します。
今回は、一部でコードの例示をしますが、基本的には設計の話になります。


「値オブジェクト」は設計技法

【値オブジェクト(ValueObject)】は、オブジェクト指向プログラミングでの、詳細設計(クラス設計)・コーディング設計の技法です。
大事なことなので繰り返します。「設計の技法」です。

「えっ、コーディングの技法じゃないの?」という声が上がりそうですね。
オブジェクト指向も、コーディング技法と勘違いしている人が多いと感じています。
その理由は「オブジェクト指向言語」を使ってコードを書いているからでしょう。

「オブジェクト指向言語」とは、「オブジェクト指向で設計したものを、そのままコードで実装できる言語」のことです。
最初期の段階で、言語が先行していたかもしれません。
オブジェクト指向の目的は「オブジェクトとして世の中の事柄を表現する」とです。
これは設計の技法に他ならないですよね!

値オブジェクトは、オブジェクト指向の延長線上にあるものです。
僕個人の感想ですが、現在における「オブジェクト指向の極地」だと感じています。
つまり、オブジェクト指向と同様に、設計技法になるわけですね。

オブジェクト指向と設計については、また別の機会を設けて解説する予定です。
今回は、結論だけの強引な話で勘弁してください。


「値オブジェクト」が表現するもの

値オブジェクトは、「プリミティブな型」をラッピングし「個々に意味のある型」として扱います。
実際に、価格を表現した値オブジェクトのコード例を見てみましょう。

// 価格のバリューオブジェクト
public class Price {

    private int _Price;  // 価格を内部的に保持する

    // コンストラクタ 価格=単価×数量
    public Price(UnitPrice unitPrice, Quantity Quantity) {
        _Price = unitPrice.ToInt() * Quantity.ToInt();
    }

    // 価格テキスト表示
    public string ToString() {
        return string.Fromat("{0;#,0}", _Price);      
    }
}

値オブジェクトには、主に3つの特徴があります。
僕が厳密な、値オブジェクトの定義を知りません。
ですので、これらは僕が実践してみて体感した特徴になります。

  1. 1つのクラスは、1つの物を表現する
  2. コンストラクタ以外で内部値(データ)が変わらない
  3. 内部の値・型を公開しない
  4. データと振る舞いをセットにする

それぞれの特徴を見ていきましょう。

1. 1つのクラスは、1つの物を表現する

これは、つまりこういう定義の方法をするということです。

金額型:単価, 金額型:価格, 日付型:注文日, 日付型:請求日
単価型:単価, 価格型:価格, 注文日型:注文日, 請求日型:請求日

下の様に、それぞれを用意することで、内部のデータが見えない個別のクラスを作ります。
内部のデータを外部から参照する必要はありません。
つまり「内部のデータを見る必要がない」と言い換えることもできます。

どうしてそうなるのかは、この後で説明していきます。

2. コンストラクタ以外で内部値(データ)が変わらない

値オブジェクトで内部値(データ)が変わる場合、基本的には前の値を破棄して、新しい値を作り直します。

例えば数量が変更されて価格が変更となった場合があります。
変更前の価格のインスタンスは破棄して、新しい価格のインスタンスを作ります。

変更後の数量を与えて再計算させる様なことを、価格型のオブジェクトにしません。
その様な実装をしておくことは値オブジェクトにとって一般的ではありません。
それは、価格の中に直接関係のない「単価」という情報を持つことになります。
クラス間の関係性の複雑性が増すだけですし、コーディングコストを無駄に上げるだけです。

また、内部値を変更できるようなGettr/Setterも実装しません。

内部のデータを変えないことで、不変性を担保するということです。
変数の使い回しも止めれば、それはより効果的になるでしょう。

3. 内部の値・型を公開しない

値オブジェクトが、実際にどういう型で内部にデータを保管しているのか。
そして、そのデータがなんであるのか。
こらを外部から知る方法は、実装しません。 それは、実装する必要がないからです。

大事なのは値オブジェクトの中に「こういうデータが入っている」という事だけです。
実際にデータを使用する場面において、内部値そのものが必要な場面はどれぐらいあるでしょう?

例えば、画面や帳票に、単位を付加し整形した金額を表示する時。
例えば、税率の計算などで数字を扱う時。
例えば、氏名や住所、所属部署の文字列を扱いたい時。

果たして本当に、データそのものが必要でしょうか?

計算する時は、計算用の数値ではだめでしょうか?
場合に応じて、整数型や浮動小数点型で扱えた方が便利ではありませんか?
上に例示したコードでは、「ToInt()メソッド」を利用て計算しています。
しかし、これがすなわち内部値をそのまま利用している、とはなりません。
単価型の内部値は、文字列かもしれませんし、浮動小数点型かもしれません。
あくまでも、この場では「Int型」としての数字を取り出して計算しているだけです。

数字に単位を付加して、整形する場合に必要なのは数値ですか?
整形済みの文字列が取り出せるなら、その方が便利ではありませんか?
整形フォーマット、そんなにコロコロ変わりませんよね?

今、内部値が必要と思われていることの9割は、実際に内部値は不要です。
「金額型」の様な、汎用的なクラスにしているから必要とされるのでしょう。

4. データと振る舞いをセットにする

「値オブジェクト」というのだから、データだけを管理する入れ物。
総勘違いしている人が、一定層いるかもしれません。

値オブジェクトは、データそのものと、振る舞いをセットにすることで強力な「型」としての性質を持つことができます。

例えば「請求日型」で考えてみましょう。
・ 内部値は日付型で、注文日の翌月の末日が請求日になる
・ 決算期を超える場合は、帳簿の扱いが変わる為、これでフィルタリングが必要
・ 一覧には「yyyy.MM.dd」形式、単票には「ggyy年M月d日」形式で表記

これらの仕様が、そのまま振る舞いになります。
都度コーディングするのではなく、staticなツールメソッドで実現するのでもなく。
これらを値オブジェクトとしての「請求日型」のメソッドとして実装してしまう、ということです。

このまでの内容に関連して、過去に値オブジェクトについて触れたときに、この様なコメントを頂いています。

『値オブジェクトは「データの属性」をそのまま「オブジェクトの型」』のところは「データの属性」より「データの属性値」かなと思いました。

言葉の印象だけで言えば「属性値」というのは「値そのものだけ」を指します。
この様に、振る舞いまでセットにすることで「データの持つ性質」までを表現した「データ属性」としての意味となる、と僕は考えています。


「DDD」と「値オブジェクト」

「値オブジェクト」とセットで語られる事が多い技術に「ドメイン駆動設計(DDD)」があります。
僕も、値オブジェクトを初めて知ったのは、DDDを学ぶ中で、でした。
一部には「DDD=値オブジェクト」とする人達もおり、DDDの文脈の中から値オブジェクトだけを取り込み、プログラマーレベルで導入できるDDDを「軽量DDD」とする動きもあります。

僕は、この一部の動きがだいっ嫌いです。

僕の理解では「DDD≠値オブジェクト」です。
「DDD」は、単一モデルを利用する、プロジェクト運営のマネジメント技術です。
プログラマだけでDDDはできません。
もちろん、値オブジェクトを採用してクラス設計を行うことに問題を感じません。
それを「軽量DDD」と称して、DDDやってるつもりになるのは、違います。

「DDDをやりたいけど、決済権がない。それでもDDDっぽいことをやりたい」
「DDD導入前のスモールステップとして値オブジェクトだけ導入したい」
という様に、明確に違うもの、もしくは前段の1つとして取り組むなら良いでしょう。
と、僕は考えています。

むしろ、僕もそれをやっていますし、勧めているぐらいですから。


「値オブジェクト」は万能ではない

値オブジェクトのは、ありとあらゆる場面で力を発揮するものではありません。
その真価は「複雑性への対応」や「メンテナンス性の向上」です。

例えば、ループ変数に値オブジェクトを使うのは変だ、と気付くでしょう。
例えば、信号機の色の様な、状態の変化を管理するならば、列挙型を使用すべきです。
例えば、みんな大好き「イノウーの憂鬱」に登場した検温システムに、値オブジェクトはなじまないと思います。

値オブジェクトは、導入当初のコストが上昇する傾向があります。
当然ですね。システムで扱う全てのデータを型として扱うのですから。
管理するクラス(≒ファイル)が増えますし、コーディング量も増えます。

開発コストや、そのシステムの使用期間・回数。
その後のメンテナンス期間を含めて、採用を決めるべきなのです。

短期の開発であれば、システムの運用恒常性が低いのであれば。
値オブジェクトを導入するコストだけが高くなります。
メリットはあまり享受できないでしょう。

ここで、人数はわざと挙げていません。
短期で一人での開発であれば、採用する必要はないでしょう。
一人でも長期になるなら、採用を検討して良いと思います。
何故なら、半月前の自分が書いたコードは別人のコードだからです。
つまり、人数より期間の方が検討要因としての比重が大きいからです。

結局は、値オブジェクトは設計技法なのです。
つまりは、マネジメントの影響を大きく受けるわけです。
個々のエンジニアのわがままで好きにやっていいレベルのものではないのです。

以上!




ITエンジニアの視点で、時事ニュースを5分間で紹介する動画を平日ほぼ毎日公開してます。
「日々の生活の中にエンジニアリングがある」からこそ、
身近な時事ニュースから学ぶことが重要です。

#ほぼ日ITエンジニアニュース

仕事の都合により、今は動画投稿をお休みしています。

Comment(0)

コメント

コメントを投稿する