プログラムにおいて「データの整合性を守りつつ、意図しない変更を防ぐ」ことは重要です。C#のValue Object(バリューオブジェクト)パターンを使うことで、値として扱うオブジェクトを不変にし、安全なプログラムを構築できます。
本記事では、Value Objectパターンの基本概念、実装方法、メリットをわかりやすく解説します。
目次
Value Objectパターンとは?
Value Object(バリューオブジェクト)パターンとは、「値としての特性を持ち、不変(immutable)なオブジェクト」を作るデザインパターンです。
- 値が同じなら同一とみなす(IDを持たない)
- 作成後に変更できない(不変オブジェクト)
- オブジェクト指向のプログラムをシンプルにする
例えば、「価格」「日付」「座標」「氏名」などの値を扱う場合に適しています。
Value Objectパターンのメリット
① 不変(Immutable)で安全
オブジェクトが変更されると意図しないバグが発生しやすくなりますが、Value Objectは値の変更ができないため、安全性が向上します。
② 等価比較が簡単
「ID」ではなく「値そのもの」が等価性を決めるため、比較がシンプルになります。
③ 再利用しやすい
共通の概念(例: 住所、金額、通貨)をカプセル化することで、コードの重複を減らし、保守性を向上できます。
C#での実装方法
C#でValue Objectを実装する際、不変性を確保するために以下のポイントを押さえます。
✅ 実装のポイント
- フィールドはprivate、コンストラクタで初期化
- 値の変更を許可しない(readonly)
- EqualsとGetHashCodeを適切にオーバーライド
- レコード型(C# 9.0以降)を活用するのも有効
public class Money
{
public decimal Amount { get; }
public string Currency { get; }
public Money(decimal amount, string currency)
{
if (amount < 0) throw new ArgumentException("金額は負の値にできません。");
if (string.IsNullOrWhiteSpace(currency)) throw new ArgumentException("通貨を指定してください。");
Amount = amount;
Currency = currency;
}
public override bool Equals(object obj)
{
if (obj is Money other)
{
return Amount == other.Amount && Currency == other.Currency;
}
return false;
}
public override int GetHashCode() => HashCode.Combine(Amount, Currency);
}
public record Money(decimal Amount, string Currency);
Value ObjectとEntityの違い
| 項目 | Value Object | Entity(エンティティ) |
|---|---|---|
| 識別子(ID) | 持たない(値がすべて) | 持つ(IDで識別) |
| 変更可能性 | 不変(Immutable) | 変更可能(Mutable) |
| 比較方法 | 値が同じなら等しい | IDが同じなら等しい |
| 用途 | 金額、住所、日付 | ユーザー、注文、商品 |
例えば、「ユーザーの氏名」はValue Objectですが、「ユーザー」そのものはIDを持つためEntityになります。
まとめ
Value Objectパターンのポイント
- 値が同じなら同一とみなす
- 不変(Immutable)なので安全
- 等価比較がシンプル
- C#ではrecord型を使うと簡単に実装できる
- Entityとは「IDを持つかどうか」で異なる
Value Objectパターンを活用することで、C#のプログラムを安全でシンプルな構造にできます。特に不変性を意識することで、バグの少ない設計が可能になります。今後の開発で、ぜひValue Objectを取り入れてみてください!
