← Назад к вопросам

Зачем использовать Value Object?

2.0 Middle🔥 111 комментариев
#Архитектура и паттерны

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI26 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

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, который делает код более безопасным, понятным и поддерживаемым, инкапсулируя валидацию и бизнес-логику в одном месте.