本文目录导读:

在PHP中,实体(Entity)和值对象(Value Object)是领域驱动设计(DDD)中的两个核心概念,我来详细讲解它们的定义和区别。
实体(Entity)
核心特征
- 有唯一标识:具有ID,用于区分不同对象
- 可变:属性可以修改,但有生命周期
- 相同性:通过ID判断是否相等,而非属性值
基础定义
<?php
// 简单的实体示例
class User
{
private string $id;
private string $name;
private string $email;
private DateTimeImmutable $createdAt;
private DateTimeImmutable $updatedAt;
public function __construct(
string $id,
string $name,
string $email
) {
$this->id = $id;
$this->name = $name;
$this->email = $email;
$this->createdAt = new DateTimeImmutable();
$this->updatedAt = new DateTimeImmutable();
}
// getter方法
public function getId(): string
{
return $this->id;
}
public function getName(): string
{
return $this->name;
}
// 实体方法 - 修改行为
public function changeName(string $newName): void
{
$this->name = $newName;
$this->updatedAt = new DateTimeImmutable();
}
// 相等性比较
public function equals(self $other): bool
{
return $this->id === $other->id;
}
}
带业务逻辑的实体
<?php
class Order
{
private string $id;
private array $items;
private string $status;
private float $totalAmount;
public function __construct(string $id)
{
$this->id = $id;
$this->items = [];
$this->status = 'pending';
$this->totalAmount = 0;
}
public function addItem(OrderItem $item): void
{
$this->items[] = $item;
$this->recalculateTotal();
}
public function confirm(): void
{
if ($this->status !== 'pending') {
throw new DomainException('只能确认待处理的订单');
}
$this->status = 'confirmed';
}
private function recalculateTotal(): void
{
$this->totalAmount = array_sum(
array_map(fn($item) => $item->getTotal(), $this->items)
);
}
}
值对象(Value Object)
核心特征
- 无唯一标识:没有ID
- 不可变:创建后不能修改
- 相同性:通过所有属性值判断是否相等
- 自包含:作为整体使用
基础定义
<?php
// 简单的值对象示例
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 getAmount(): float
{
return $this->amount;
}
public function getCurrency(): string
{
return $this->currency;
}
// 值对象方法 - 返回新实例
public function add(Money $other): Money
{
if ($this->currency !== $other->currency) {
throw new DomainException('不能添加不同货币的金额');
}
return new Money(
$this->amount + $other->amount,
$this->currency
);
}
// 保证不可变性
public function withAmount(float $newAmount): Money
{
return new Money($newAmount, $this->currency);
}
// 相等性比较
public function equals(Money $other): bool
{
return $this->amount === $other->amount
&& $this->currency === $other->currency;
}
public function __toString(): string
{
return sprintf('%s %s', number_format($this->amount, 2), $this->currency);
}
}
复杂值对象
<?php
// 地址值对象
class Address
{
private string $street;
private string $city;
private string $state;
private string $postalCode;
public function __construct(
string $street,
string $city,
string $state,
string $postalCode
) {
$this->street = $street;
$this->city = $city;
$this->state = $state;
$this->postalCode = $postalCode;
$this->validate();
}
private function validate(): void
{
if (strlen($this->street) < 5) {
throw new InvalidArgumentException('街道地址太短');
}
if (!preg_match('/^[A-Za-z0-9-]+$/', $this->postalCode)) {
throw new InvalidArgumentException('无效的邮政编码');
}
}
// 所有属性都是只读的
public function getStreet(): string
{
return $this->street;
}
public function getCity(): string
{
return $this->city;
}
public function getState(): string
{
return $this->state;
}
public function getPostalCode(): string
{
return $this->postalCode;
}
// 完整相等性比较
public function equals(Address $other): bool
{
return $this->street === $other->street
&& $this->city === $other->city
&& $this->state === $other->state
&& $this->postalCode === $other->postalCode;
}
}
常用的模板方法
实体基类模板
<?php
abstract class BaseEntity
{
protected string $id;
protected DateTimeImmutable $createdAt;
protected DateTimeImmutable $updatedAt;
abstract public function getId(): string;
protected function __construct()
{
$this->createdAt = new DateTimeImmutable();
$this->updatedAt = new DateTimeImmutable();
}
protected function markUpdated(): void
{
$this->updatedAt = new DateTimeImmutable();
}
public function getCreatedAt(): DateTimeImmutable
{
return $this->createdAt;
}
public function getUpdatedAt(): DateTimeImmutable
{
return $this->updatedAt;
}
}
值对象基类模板
<?php
abstract class BaseValueObject
{
abstract public function equals($other): bool;
public function __toString(): string
{
return json_encode($this->toArray());
}
abstract protected function toArray(): array;
}
实际应用示例
实体中的值对象
<?php
class Product extends BaseEntity
{
private string $name;
private Money $price; // 值对象
private Address $warehouseAddress; // 值对象
private string $sku;
public function __construct(
string $id,
string $name,
Money $price,
Address $warehouseAddress
) {
parent::__construct();
$this->id = $id;
$this->name = $name;
$this->price = $price;
$this->warehouseAddress = $warehouseAddress;
$this->sku = $this->generateSku($name);
}
public function updatePrice(Money $newPrice): void
{
if ($newPrice->getAmount() < $this->price->getAmount()) {
// 降价的业务逻辑
}
$this->price = $newPrice;
$this->markUpdated();
}
public function getId(): string
{
return $this->id;
}
private function generateSku(string $name): string
{
return strtoupper(substr($name, 0, 3)) . '-' . $this->id;
}
}
// 使用示例
$price = new Money(99.99, 'USD');
$address = new Address('123 Main St', 'New York', 'NY', '10001');
$product = new Product('prod-001', 'Excellent Product', $price, $address);
$product->updatePrice(new Money(89.99, 'USD')); // 实体属性变化
// 值对象是不可变的
$newPrice = new Money(79.99, 'USD');
// 不能直接修改 $price->amount,而是创建新的Money实例
选择标准
什么时候用实体?
<?php
// ✅ 应使用实体的情况
class User
{
public function changeEmail(string $newEmail): void
{
// 它需要跟踪身份,属性可以变化
}
}
class Question
{
public function updateContent(string $newContent): void
{
// 它有ID,可以修改
}
}
什么时候用值对象?
<?php
// ✅ 应使用值对象的情况
class Color
{
private string $hexValue;
// 颜色没有身份,值相同就是同一个颜色
}
class Coordinates
{
private float $latitude;
private float $longitude;
// 坐标是整体,不可变
}
class EmailAddress
{
private string $email;
// 验证逻辑封装在内部
}
最佳实践
<?php
// 1. 实体:负责身份和生命周期管理
final class Customer extends BaseEntity
{
public function __construct(string $id)
{
parent::__construct();
$this->id = $id;
}
public function getId(): string
{
return $this->id;
}
// 业务行为
}
// 2. 值对象:封装值而不变性
final class FullName extends BaseValueObject
{
public function __construct(
private readonly string $firstName,
private readonly string $lastName
) {}
public function getFullName(): string
{
return "{$this->firstName} {$this->lastName}";
}
public function equals($other): bool
{
if (!$other instanceof self) {
return false;
}
return $this->firstName === $other->firstName
&& $this->lastName === $other->lastName;
}
protected function toArray(): array
{
return [
'firstName' => $this->firstName,
'lastName' => $this->lastName
];
}
}
| 特征 | 实体 | 值对象 |
|---|---|---|
| 标识 | 必须有ID | 不需要ID |
| 可变性 | 可变的 | 不可变的 |
| 相等性 | 通过ID | 通过所有属性 |
| 生命周期 | 有自己的生命周期 | 依附于实体 |
| 存储 | 通常存在数据库表中 | 通常作为实体的属性 |
| 例子 | User, Order, Product | Money, Address, Color |
理解并正确使用实体和值对象,可以让你的PHP代码更加符合领域驱动设计的理念,提高代码的可维护性和可测试性。
版权声明: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直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播