Зачем использовать Value Object?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Value Object — Значимый объект
Value Object (VO) — паттерн проектирования из Domain-Driven Design (DDD), который представляет простой объект, чей смысл определяется его значением, а не идентичностью. Value Object — это фундаментальная концепция для написания чистого, типобезопасного кода в PHP.
Что такое Value Object?
Value Object — это объект, который:
- Не имеет идентичности (ID) — два объекта равны, если их значения равны
- Неизменяем (immutable) — после создания не может быть изменён
- Сравнивается по значению, а не по ссылке
- Инкапсулирует бизнес-логику и валидацию
Пример: без Value Object (плохо)
class User {
public string $email;
public string $phone;
public float $price;
public function __construct(string $email, string $phone, float $price) {
$this->email = $email;
$this->phone = $phone;
$this->price = $price;
}
}
// Проблемы:
// 1. Email может быть любой строкой (без валидации)
// 2. Phone может быть некорректным
// 3. Price может быть отрицательным
// 4. Нет переиспользуемой логики валидации
// 5. Сложно тестировать
$user = new User('not-valid-email', '123', -100);
Решение с Value Object
final class Email {
private string $value;
public function __construct(string $value) {
if (!filter_var($value, FILTER_VALIDATE_EMAIL)) {
throw new InvalidArgumentException('Invalid email format');
}
$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;
}
}
final class PhoneNumber {
private string $value;
public function __construct(string $value) {
// Валидация номера телефона
if (!preg_match('/^\+?\d{10,15}$/', $value)) {
throw new InvalidArgumentException('Invalid phone number');
}
$this->value = $value;
}
public function getValue(): string {
return $this->value;
}
public function equals(PhoneNumber $other): bool {
return $this->value === $other->value;
}
}
final class Money {
private float $amount;
public function __construct(float $amount) {
if ($amount < 0) {
throw new InvalidArgumentException('Amount cannot be negative');
}
$this->amount = $amount;
}
public function getAmount(): float {
return $this->amount;
}
public function add(Money $other): Money {
return new Money($this->amount + $other->amount);
}
public function equals(Money $other): bool {
return $this->amount === $other->amount;
}
}
// Использование:
class User {
public function __construct(
private Email $email,
private PhoneNumber $phone,
private Money $price
) {}
}
// Валидация происходит при создании
$email = new Email('user@example.com'); // OK
// new Email('invalid'); // InvalidArgumentException
$user = new User($email, new PhoneNumber('+1234567890'), new Money(99.99));
Преимущества Value Object
1. Валидация в одном месте — все проверки в конструкторе VO
// Всегда гарантированно корректное значение
$email = new Email($userInput); // Валидация здесь
2. Типобезопасность — код становится более явным и понятным
// Вместо функции(string, string, float)
public function createUser(Email $email, PhoneNumber $phone, Money $price)
3. Переиспользуемость — логику валидации можно применять везде
// Email используется в разных доменных объектах
class Order {
public function __construct(private Email $customerEmail) {}
}
4. Инкапсуляция бизнес-логики
class Money {
public function add(Money $other): Money {
return new Money($this->amount + $other->amount);
}
}
$total = $price1->add($price2);
5. Тестируемость — легче писать модульные тесты
public function testEmailValidation() {
$this->expectException(InvalidArgumentException::class);
new Email('invalid');
}
6. Неизменяемость — устраняет целый класс багов
// Нельзя случайно изменить значение
// $email->value = 'new@example.com'; // Невозможно
Best Practices для Value Object
- Делай их final (не наследуй)
- Используй private для полей
- Реализуй equals() для сравнения
- Реализуй __toString() для удобства
- Обеспечь полную неизменяемость (immutability)
- Валидируй в конструкторе, бросай исключение при ошибке
- Используй в типах для явности
Когда использовать Value Object
- Когда есть специфическая валидация значения
- Когда нужна бизнес-логика над простыми типами
- Когда значение переиспользуется в нескольких местах
- Для денежных сумм, координат, дат, email и прочих доменных типов
Вывод: Value Object — мощный инструмент DDD, который делает код более безопасным, понятным и поддерживаемым, инкапсулируя валидацию и бизнес-логику в одном месте.