ドメイン駆動設計(DDD)を学び始めたとき、最初に出てくるのが「Entityと値オブジェクト」という概念です。名前だけ聞いてもピンとこないし、いざコードに落とし込もうとすると「結局これはEntityなの?値オブジェクトなの?」と余計に迷ってしまうことが多くありました。
特に、自分のような学び始めの初心者は以下のような疑問がずっと頭にありました:
- オブジェクトの中の値が途中で変わるようなものって、Entityってこと?
- IDがあるクラスは全部Entityって思ってもいい?
- 値オブジェクトってどこまで小さくしていいの?
この記事では、自分が学習する中で「ここが理解の壁だった」と感じたポイントを整理しながら、Entityと値オブジェクトの違いを明確に掴めるように書いていきます。
DDDをこれから学び始める方にとって、少しでも理解の助けになれば幸いです。
「結局、何が違うの?」という疑問に応えるために、Entityと値オブジェクトをいくつかの観点から並べて比べてみました。
ここでは代表的な例として、顧客(Customer) をEntity、住所(Address) を値オブジェクトとして取り上げています。
観点 | Entity(例:顧客) | 値オブジェクト(例:住所) |
---|---|---|
識別子 (ID) | 顧客は一意に識別されるID(例:顧客ID)を必ず持つ。 たとえ名前や住所が同じでも別人として扱う。 |
住所自体はIDを持たない。 「東京都千代田区1-1」はどの顧客のものでも、同じ住所なら同一と見なす。 |
同一性の判断 | 「同じ顧客かどうか」はIDが同じかどうかで判断する。 属性が変わっても同じIDであれば同一人物。 |
「同じ住所かどうか」は値が完全に一致しているかで判断する。 すべてのフィールドが一致すれば同一。 |
状態変化 | 名前や住所、メールアドレスなど、時間とともに状態が変わることがある。 | 一度作られたら基本的に変更しない(immutable)。 変更したい場合は新しいインスタンスを作る。 |
ライフサイクル | 会員登録から退会までのように長い期間使われることが多い。 | 一時的な利用(請求先住所など)に使われ、必要がなくなれば破棄される。 |
設計の目的 | 「顧客」としての行動・状態を表現し、振る舞い(メソッド)を持たせることもある。 | 意味やルールを持つ値を、ただの string や int で表すのではなく、専用のクラス(値オブジェクト)として明示的に設計する。 |
ここでは「顧客(Customer)」をEntity、「住所(Address)」を値オブジェクトとして、それぞれをPHPで表現してみます。
1. 値オブジェクト(Value Object)としての住所
final class Address
{
private string $prefecture;
private string $city;
private string $street;
public function __construct(string $prefecture, string $city, string $street)
{
$this->prefecture = $prefecture;
$this->city = $city;
$this->street = $street;
}
public function isEqual(Address $other): bool
{
return $this->prefecture === $other->prefecture &&
$this->city === $other->city &&
$this->street === $other->street;
}
public function __toString(): string
{
return "{$this->prefecture}{$this->city}{$this->street}";
}
}
isEqual() は値オブジェクトにとって重要な同値性比較を実装。
immutable(変更不可)な設計なので、値を変更したい場合は新しいインスタンスを作るのが基本。
2. エンティティ(Entity)としての顧客
class Customer
{
private string $id;
private string $name;
private Address $address;
public function __construct(string $id, string $name, Address $address)
{
$this->id = $id;
$this->name = $name;
$this->address = $address;
}
public function changeAddress(Address $newAddress): void
{
$this->address = $newAddress;
}
public function getId(): string
{
return $this->id;
}
public function getAddress(): Address
{
return $this->address;
}
public function equals(Customer $other): bool
{
return $this->id === $other->getId();
}
}
id によって「同一顧客かどうか」を判断します(属性が変わっても同じIDなら同一人物)。
状態(nameやaddressなど)は時間とともに変わる可能性があります。
3. 実際の利用例
$address1 = new Address("東京都", "千代田区", "1-1");
$customer = new Customer("cust-001", "田中 太郎", $address1);
$address2 = new Address("東京都", "千代田区", "2-2");
$customer->changeAddress($address2);
このコードでは、顧客の住所を「新しい値オブジェクト」として差し替えることで、Entityの状態が変化する様子を表現しています。
Entityと値オブジェクトの違いは、名前だけではなかなかピンときませんが、実際にコードに落とし込んでみることでその役割の違いが明確になります。
Entity は「同一性(ID)」を持ち、「状態が変化するもの」を表現します。たとえば、顧客(Customer)は名前や住所が変わることがあっても、IDが同じなら「同じ人物」です。
値オブジェクト は「同一性を持たず」、「意味ある値の集合」を表現します。たとえば住所(Address)は内容が同じなら同一とみなし、変更時は新しく作り直すのが基本です。
このように、どちらを使うべきか迷ったときは、
それが「何者か」を表したいなら → Entity
それが「何か」を表したいだけなら → 値オブジェクト
という視点がとても参考になるかと思います。
最初の疑問をまとめると・・・
疑問 | 考え方・答え |
---|---|
オブジェクトの中の値が途中で変わるようなものって、Entityってこと? | そう考えてよい。状態が時間とともに変わるような存在であれば、Entityとして扱う。 例えば changeAddress() のように、インスタンスの状態を更新するメソッドを持つのが典型的なEntityの特徴。 |
IDがあるクラスは全部Entityって思ってもいい? | 単にIDがあるだけでは不十分。IDによって同一性を判断する必要があるかが重要。 たとえば equals() メソッドでIDを比較して同一性を担保する設計になっていれば、それはEntityと考えてよい。 |
値オブジェクトってどこまで小さくしていいの? | 意味やルールがあるなら小さくても値オブジェクトにしてよい。 たとえば Address クラスの「都道府県」や「市区町村」などに形式ルールがあるなら、それぞれを独立したクラスにして、バリデーションや equals() を実装することで、1つの値でも値オブジェクトとしての意味を持たせられる。 |
Views: 0