Интересно... Я для чего ссылки даю в своих рассуждениях, не для
Invis1ble же, он и так это всё знает. Почитал бы сначала, прежде чем такие вопросы задавать.
Но мне не лень, я расскажу.
Суть паттерна Value Object (VO) заключается в том, чтобы представить простые типы данных в виде объекта. Именно простые - строки, числа, даже можно boolean. И вести себя эти объекты должны как простые типы. Как ты отнаследуешься от строки или числа? Потому там и финал, чтобы наследование запретить.
Самый примитивный пример:
final class AnyString
{
private $value = 'Строка';
public function __toString(): string
{
return $this->value;
}
}
Получить значение этого объекта можно только обратившись к нему, как к строке. Что нам и требовалось. Можно вместо __toString() использовать геттер, который будет возвращать только нужный нам тип.
final class AnyInt
{
private $value = 10;
public function get(): int
{
return $this->value;
}
}
Можно быть уверенным, что это int и ни что иное.
Можно иметь несколько значений, это уже тип посложнее. Но основное требование к VO, это как раз иммутабельность. Тоесть состояние объекта ни при каких обстоятельствах не может быть изменено после создания.
В чем фишка.
1. В том, что в ООП
всё должно быть объектом. И хотя это невозможно, но приверженцы чистого ООП (в частности DDD) стараются максимально это сделать. Так удобнее строить доменную модель и разговаривать на Ubiquitous Language
2. Удобно проверять не только абстрактный тип, но и более конкретный. Допустим если написать так:
function construct (string $username)
{
$this->Username = $username;
}
то аргументом может быть любая строка. А если так:
function construct (Username $username)
{
$this->Username = $username;
}
то не просто строка, а именно строка юзернейма.
3. Внутрь объекта можно поместить методы, которые относятся
только к этому значению.
Что бы не распылять код по внешним плагинам. Допустим ту же валидацию. Или, как в примере с деньгами, можно сконвертировать валюту. Это как раз и предмет наших разночтений ситуации с
Invis1ble. Он считает, что валидация должна производиться снаружи, хотя Фаулер для того и придумал этот паттерн, чтобы максимально собрать функционал, относящийся только к данному значению, вокруг этого значения, не размазывая код по сервисам.
Ничего она не требует. Посмотри внимательно на код. Максимально упрощаю:
final class Username
{
private $value;
private $checker = true;
public function withName(string $username): Username
{
if (empty($this->checker)) {
throw new \LogicException('Низя.');
}
$clone = clone $this;
$clone->value = $username;
unset($clone->checker);
return $clone;
}
public function __toString(): string
{
return $this->value;
}
}
Теперь можешь пробрасывать его куда хочешь.
class Controller {
function construct (Username $username) {
$this->Username = $username;
}
function add ($name) {
$newName = $this->Username->withName($name);
}
}
В $newName ты получишь
новый объект со значением $name. Заполненный клон. А шаблон $this->Username останется нетронутым. И готовым к повторному использованию. Это, в совокупности с final и обеспечивает иммутабельность.
Invis1ble сделал еще жесче - вообще запретил создание новых объектов, сделав конструктор приватным. Но за это приходится "расплачиваться" фабрикой и рефлексией. :)
_____________
Если вам недостаточно собственных заблуждений, можно расширить их мнениями экспертов.Нужно уважать мнение оппонета. Ведь заблуждаться - его святое право.Настаивал, настаиваю и буду настаивать на своем. На кедровых орешках.