PHP项目中的值对象与实体
基本概念
实体(Entity) 和 值对象(Value Object) 是领域驱动设计(DDD)中的核心概念:

实体:
- 有唯一标识(ID),即使所有属性相同,不同ID就是不同对象
- 可变(Mutable),可以修改内部状态
- 通过ID判断相等性
值对象:
- 没有唯一标识,由所有属性值决定其身份
- 不可变(Immutable),一旦创建不可修改
- 通过所有属性值判断相等性
代码示例
实体示例
<?php
class User {
private UserId $id;
private string $name;
private Email $email;
public function __construct(UserId $id, string $name, Email $email) {
$this->id = $id;
$this->name = $name;
$this->email = $email;
}
public function getId(): UserId {
return $this->id;
}
public function getName(): string {
return $this->name;
}
// 允许修改名称(可变性)
public function changeName(string $name): void {
$this->name = $name;
}
// 通过ID判断相等
public function equals(User $other): bool {
return $this->id->equals($other->id);
}
}
class UserId {
private string $id;
public function __construct(string $id) {
$this->id = $id;
}
public function equals(UserId $other): bool {
return $this->id === $other->id;
}
}
值对象示例
<?php
class Email {
private string $value;
public function __construct(string $value) {
// 验证
if (!filter_var($value, FILTER_VALIDATE_EMAIL)) {
throw new InvalidArgumentException('无效的邮箱地址');
}
$this->value = $value;
}
// 不可变,只能获取值
public function getValue(): string {
return $this->value;
}
// 通过所有属性判断相等
public function equals(Email $other): bool {
return $this->value === $other->value;
}
// 转换为字符串
public function __toString(): string {
return $this->value;
}
}
class Address {
private string $street;
private string $city;
private string $zipCode;
public function __construct(string $street, string $city, string $zipCode) {
$this->street = $street;
$this->city = $city;
$this->zipCode = $zipCode;
}
// 不可变,只提供getter方法
public function getStreet(): string { return $this->street; }
public function getCity(): string { return $this->city; }
public function getZipCode(): string { return $this->zipCode; }
public function equals(Address $other): bool {
return $this->street === $other->street
&& $this->city === $other->city
&& $this->zipCode === $other->zipCode;
}
}
class Money {
private float $amount;
private string $currency;
public function __construct(float $amount, string $currency) {
if ($amount < 0) {
throw new InvalidArgumentException('金额不能为负数');
}
$this->amount = $amount;
$this->currency = $currency;
}
// 返回新的值对象,而不是修改原有对象
public function add(Money $other): Money {
if ($this->currency !== $other->currency) {
throw new InvalidArgumentException('货币类型不同');
}
return new Money($this->amount + $other->amount, $this->currency);
}
public function equals(Money $other): bool {
return $this->amount === $other->amount
&& $this->currency === $other->currency;
}
}
实际应用场景
订单系统中的示例
<?php
// 实体:订单
class Order {
private OrderId $id;
private CustomerId $customerId;
private OrderStatus $status;
private array $lineItems = [];
private Money $totalAmount;
public function __construct(OrderId $id, CustomerId $customerId) {
$this->id = $id;
$this->customerId = $customerId;
$this->status = new OrderStatus('pending');
$this->totalAmount = new Money(0, 'CNY');
}
public function addLineItem(LineItem $item): void {
$this->lineItems[] = $item;
$this->recalculateTotal();
}
public function cancel(): void {
$this->status = new OrderStatus('cancelled'); // 状态是值对象,不可变
}
public function getId(): OrderId {
return $this->id;
}
private function recalculateTotal(): void {
$total = new Money(0, 'CNY');
foreach ($this->lineItems as $item) {
$total = $total->add($item->getSubtotal());
}
$this->totalAmount = $total;
}
public function equals(Order $other): bool {
return $this->id->equals($other->id); // 实体通过ID比较
}
}
// 值对象:订单行项目
class LineItem {
private ProductId $productId;
private int $quantity;
private Money $unitPrice;
public function __construct(ProductId $productId, int $quantity, Money $unitPrice) {
$this->productId = $productId;
$this->quantity = $quantity;
$this->unitPrice = $unitPrice;
}
public function getSubtotal(): Money {
$amount = $this->quantity * $this->unitPrice->getAmount();
return new Money($amount, $this->unitPrice->getCurrency());
}
public function equals(LineItem $other): bool {
return $this->productId === $other->productId
&& $this->quantity === $other->quantity
&& $this->unitPrice->equals($other->unitPrice);
}
}
// 值对象:订单状态
class OrderStatus {
private string $value;
private const VALID_STATUSES = ['pending', 'paid', 'shipped', 'cancelled'];
public function __construct(string $value) {
if (!in_array($value, self::VALID_STATUSES)) {
throw new InvalidArgumentException('无效的订单状态');
}
$this->value = $value;
}
public function getValue(): string {
return $this->value;
}
public function equals(OrderStatus $other): bool {
return $this->value === $other->value;
}
}
选择指南
什么时候使用实体:
- 对象需要唯一标识(如用户、订单、产品)
- 对象的身份会随时间改变属性而变化
- 需要跟踪对象的生命周期
什么时候使用值对象:
- 描述对象的属性或特征(如地址、金额、颜色)
- 没有独立生命周期的概念
- 经常被共享和重用
- 通常通过构造或工厂方法创建
最佳实践
- 值对象必须不可变 - 所有修改操作都应返回新实例
- 实体可以有可变状态,但修改应通过命令方法进行
- 值对象应包含自我验证逻辑
- 值对象可以包含业务逻辑,如金额计算
- 实体使用ID比较,值对象使用属性比较
- 优先使用值对象而非基元类型(如用Email类代替string)
这种设计模式使代码更加清晰、可维护,并减少bug。
版权声明:NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播