Учитывая Address
минимум должен иметь $firstLine
а также $postcode
но может содержать дополнительные свойства,
Я ищу для реализации builder
чтобы облегчить строительство Address
,
Сокращенный Address
может выглядеть так:
class Address
{
/**
* @var AddressLine
*/
private $firstLine;
/**
* @var null|AddressLine
*/
private $secondLine;
/**
* Other properties excluded for brevity
*/
...
/**
* @var Postcode
*/
private $postcode;
/**
* @param AddressLine $firstLine
* @param null|AddressLine $secondLine
* ...
* @param Postcode $postcode
*/
public function __construct(AddressLine $firstLine, AddressLine $secondLine, ... , Postcode $postcode)
{
$this->firstLine = $firstLine;
$this->secondLine = $secondLine;
...
$this->postcode = $postcode;
}
public static function fromBuilder(AddressBuilder $builder)
{
return new self(
$builder->firstLine(),
$builder->secondLine(),
... ,
$builder->postcode()
);
}
}
Кажется, вышесказанное имеет смысл для меня, общественности constructor
который защищает свои инварианты через typehints
и допуская традиционное строительство, дополнительно фабричный метод, который принимает AddressBuilder
это может выглядеть примерно так:
class AddressBuilder
{
public function __construct(...)
{
}
public function withSecondLine(...)
{
}
public function build()
{
return Address::fromBuilder($this);
}
}
Что касается AddressBuilder
, если он принимает приматы, которые проверены в течение build()
метод, или он должен ожидать соответствующий Value Object
?
С примитивами
public function withSecondLine(string $line)
{
$this->secondLine = $line;
}
public function build()
{
...
$secondLine = new AddressLine($this->secondLine);
return new Address(... , $secondLine, ...);
}
С объектами значения
public function withSecondLine(AddressLine $secondLine)
{
$this->secondLine = $secondLine;
}
public function build()
{
return Address::fromBuilder($this);
}
Что касается AddressBuilder, должен ли он принимать примитивы, которые проверяются во время метода build (), или он должен ожидать соответствующий объект Value?
Любой подход в порядке.
Работа с примитивами имеет тенденцию быть лучшей, когда вы находитесь на границе своего приложения. Например, когда вы читаете данные из полезной нагрузки запроса http, API, выраженный в независимых от домена примитивах, вероятно, будет проще работать с API, выраженным в типах домена.
По мере того, как вы приближаетесь к ядру приложения, работа на доменном языке приобретает все больший смысл, поэтому ваш API, скорее всего, это отразит.
Один из способов думать об этом состоит в том, что этот шаблон компоновщика в значительной степени является деталью реализации. Потребитель в простых случаях — это просто функция
BowlingGame buildMeABowlingGameForGreatGood(int.... rolls) {
BowlingGame.Builder builder = ...
rolls.forEach(r -> {
builder.addRoll(r)
} )
return builder.build();
}
и потребители функции не заботятся о деталях.
У вас могут даже быть разные API-интерфейсы построителя, так что различные контексты клиента могут вызывать наиболее подходящий.
BowlingGame buildMeABowlingGameForGreatGood(int.... rolls) {
BowlingGame.PrimitiveBuilder primitiveBuilder = new PrimitiveBuilder(
new BowlingGame.ModelBuilder(...)
);
// ...
}
Вещи могут стать интересными, если вы не уверены, что аргументы пройдут проверку на валидацию.
AddressBuilder builder = ...
// Do you want to reject an invalid X here?
builder.withSecondLine(X)
// Or do you prefer to reject an invalid X here?
builder.build()
Шаблон компоновщика дает вам указатель на изменяемое состояние выполняемой сборки, которое вы можете передать. Итак build
Заявление может быть произвольно далеко от withSecondLine
заявление. Если вы уже знаете, что X
допустимо (потому что это уже объект значения модели), тогда это, вероятно, не имеет большого значения. Если X
это примитив, тогда вам может быть все равно.
Конструктор не является частью парадигмы проектирования, управляемой доменом, поскольку он не может быть выражен как часть вездесущего языка вашего домена.
Если вы хотите использовать DDD, вы должны использовать фабрику (например, фабрику статических методов, фабрику обслуживания или какую-то другую фабрику) или репо, если вы десериализуете из какого-то источника.
Чтобы ответить на ваш конкретный вопрос о проверке, хотя: Нет, вы не проверяете свою сущность «позже». Ваша сущность и ее свойства никогда не должны быть в недопустимом состоянии, потому что ответственность за знание вызова «проверяющего» кода будет зависеть от потребителя. Кроме того, вы не сможете сериализовать эту сущность при необходимости