Как бы я лучше и безопасно соединял родительские / дочерние объекты вместе?

Это мой демонстрационный случай:

<?php
declare(strict_types=1);

final class Order
{
/** @var array|OrderPosition[] */
private $orderPositions;

public static function fromArray($orderData)
{
assert(isset($orderData['orderId']));
assert(isset($orderData['positions']));

return
new self(
$orderData['orderId'],
array_map(
function (array $positionData): OrderPosition {
// I would like to put the "future self (Order)" alread here
return OrderPosition::fromArray($positionData);
},
$orderData['positions']
)
);
}

private function __construct(string $orderId, array $orderPositions)
{
$this->orderPositions = $orderPositions;
// what I want to avoid is:
array_walk($orderPositions, function (OrderPosition $position) {
$position->defineOwningOrder($this);
});
}
}

final class OrderPosition
{
/** @var Order */
private $owningOrder;

public static function fromArray($positionData /* + as mentioned I'd like to put the "calling" Order here already...? */)
{
return
new self(
$positionData['productId'],
$positionData['amount']
);
}

private function __construct(string $productId, int $amount)
{
// …
}

/** @internal */
public function defineOwningOrder(Order $order)
{
$this->owningOrder = $order;
}
}

Мне нравится иметь указатель на «родительский» / владеющий элемент Order в моем OrderPosition; Однако, поскольку Order считается Агрегированным Корнем, я хочу, чтобы Order отвечал за создание коллекции OrderPositions.

Как я должен поместить элемент Order в каждую позицию OrderPosition, если при создании еще нет окончательного Order?
Мой нынешний подход заключается в том, чтобы позднее установить его в ctor ордера, но это приведет к изменению OrderPosition, строго говоря.

1

Решение

У вас есть комбинация нескольких дизайнерских решений, которые находятся в конфликте:

  1. неизменяемые предметы
  2. круговая ссылка
  3. конструктор, который не отвечает за построение зависимых объектов
  4. фабричный метод, который не может видеть и изменять частичный объект

Как вы говорите, ваша текущая реализация ставит под угрозу (1), позволяя OrderPosition чтобы дополнительная ссылка была добавлена ​​позже.

Вы можете решить проблему, если уберете (2). Какова ситуация, когда у вас будет ссылка на OrderPosition и хотите перейти к Order к которому он принадлежит? Можно ли перефразировать эту ситуацию как ответственность Order, удалив круговую ссылку?

Вы можете изменить (3) так, чтобы конструктор взял информацию для создания OrderPositions, не OrderPositions самих себя. В вашем примере это было бы тривиально, но если на практике у вас есть несколько разных фабрик, подающих один конструктор, это может стать грязным.

В качестве альтернативы, если вы расслабитесь (4), вы можете передать частично построенный объект в OrderPosition конструктор / фабрика:

public static function fromArray($orderData)
{
assert(isset($orderData['orderId']));
assert(isset($orderData['positions']));

$instance = new self($orderData['orderId']);
foreach ( $orderData['positions'] as $positionData ) {
$instance->orderPositions[] = OrderPosition::fromArray($positionData, $instance);
}
return $instance;
}

Хотя это все еще выглядит как мутация, это только та же мутация, которую вы делали бы в любом конструкторе — вы создаете начальное состояние объекта.

На языке, который поддерживает перегруженные или именованные конструкторы, fromArray было бы быть конструктор, и может не использовать какую-либо реализацию с другими конструкторами. В PHP вы можете эмулировать этот шаблон с пустым private function __construct(){} и статические методы, начинающиеся с $instance = new self;

1

Другие решения

Других решений пока нет …

По вопросам рекламы [email protected]