Ответ
Value Object (VO, Объект-Значение) — это небольшой неизменяемый объект, который представляет собой составное значение. Его ключевая особенность: равенство определяется не по идентификатору, а по совпадению значений всех его свойств.
Основные характеристики, которые я реализую:
- Неизменяемость (Immutable): После создания состояние объекта нельзя изменить. Любая операция, которая должна "изменить" значение, возвращает новый экземпляр VO.
- Отсутствие идентичности: Нет поля
id. Два VO с одинаковыми значениями свойств считаются полностью взаимозаменяемыми. - Самодостаточность и валидация: Объект создается только с валидными данными. Валидация происходит в конструкторе.
Практический пример: Денежная сумма с валютой.
final class Money
{
// Свойства только для чтения гарантируют неизменяемость
public function __construct(
private readonly int $amountInCents, // Храню в копейках/центах для точности
private readonly string $currencyCode // ISO код, например 'USD', 'EUR'
) {
// Валидация при создании
if ($amountInCents < 0) {
throw new InvalidArgumentException('Amount cannot be negative.');
}
if (!in_array($currencyCode, ['USD', 'EUR', 'GBP'], true)) {
throw new InvalidArgumentException("Unsupported currency: {$currencyCode}");
}
}
// Операция возвращает НОВЫЙ объект
public function add(Money $other): self
{
if ($this->currencyCode !== $other->currencyCode) {
throw new InvalidArgumentException('Cannot add different currencies.');
}
return new self($this->amountInCents + $other->amountInCents, $this->currencyCode);
}
// Сравнение по значению, а не по идентичности
public function equals(Money $other): bool
{
return $this->currencyCode === $other->currencyCode
&& $this->amountInCents === $other->amountInCents;
}
// Геттеры для получения значений
public function getAmount(): float { return $this->amountInCents / 100; }
public function getCurrencyCode(): string { return $this->currencyCode; }
}
// Использование
$salary = new Money(500000, 'USD'); // 5000.00 USD
$bonus = new Money(100000, 'USD'); // 1000.00 USD
$total = $salary->add($bonus); // Новый объект Money(600000, 'USD')
$moneyA = new Money(1000, 'EUR');
$moneyB = new Money(1000, 'EUR');
var_dump($moneyA->equals($moneyB)); // bool(true) - они равны по значению
Где я это использовал: Для представления Email, URL, географических координат, диапазонов дат. Это предотвращает "примитивную одержимость" (Primitive Obsession) и делает код типобезопасным и выразительным.