DDD: как сохранить неизменным объект сложного значения?
Я хотел бы смоделировать Address
как объект стоимости. Так как рекомендуется сделать его неизменным, я решил не предоставлять никакого установщика, который мог бы изменить его позже.
Общий подход заключается в передаче данных в конструктор; однако, когда значение объекта довольно велико, это может стать довольно раздутым:
class Address {
public function __construct(
Point $location,
$houseNumber,
$streetName,
$postcode,
$poBox,
$city,
$region,
$country) {
// ...
}
}
Другой подход заключается в предоставлении аргументов в виде массива, в результате чего получается чистый конструктор, но это может испортить реализацию конструктора:
class Address {
public function __construct(array $parts) {
if (! isset($parts['location']) || ! $location instanceof Point) {
throw new Exception('The location is required');
}
$this->location = $location;
// ...
if (isset($parts['poBox'])) {
$this->poBox = $parts['poBox'];
}
// ...
}
}
Это также выглядит немного неестественно для меня.
Любой совет о том, как правильно реализовать довольно большой объект стоимости?
3 ответа
Основная проблема с большим списком параметров - это удобочитаемость и опасность того, что вы перепутаете параметры. Вы можете решить эти проблемы с помощью шаблона Builder, как описано в Эффективной Java. Это делает код более читабельным (особенно языки, которые не поддерживают именованные и необязательные параметры):
public class AddressBuilder {
private Point _point;
private String _houseNumber;
// other parameters
public AddressBuilder() {
}
public AddressBuilder WithPoint(Point point) {
_point = point;
return this;
}
public AddressBuilder WithHouseNumber(String houseNumber) {
_houseNumber = houseNumber;
return this;
}
public Address Build() {
return new Address(_point, _houseNumber, ...);
}
}
Address address = new AddressBuilder()
.WithHouseNumber("123")
.WithPoint(point)
.Build();
Преимущества:
- параметры названы так что это более читабельно
- Сложнее перепутать номер дома с регионом
- можете использовать свой собственный порядок параметров
- необязательные параметры могут быть опущены
Единственный недостаток, о котором я могу думать, это то, что вы забыли указать один из аргументов (не вызывая WithHouseNumber
например) приведет к ошибке времени выполнения вместо ошибки времени компиляции при использовании конструктора. Вам также следует рассмотреть возможность использования большего количества объектов-значений, таких как, например, PostalCode (в отличие от передачи строки).
На связанной ноте иногда бизнес-требования требуют изменения части объекта значения. Например, когда адрес был введен изначально, номер улицы мог быть написан с ошибкой и должен быть исправлен сейчас. Так как вы смоделировали Address как неизменный объект, здесь нет сеттера. Одним из возможных решений этой проблемы является введение "функции без побочных эффектов" в объекте значения адреса. Функция будет возвращать копию самого объекта, за исключением нового названия улицы:
public class Address {
private readonly String _streetName;
private readonly String _houseNumber;
...
public Address WithNewStreetName(String newStreetName) {
// enforce street name rules (not null, format etc)
return new Address(
newStreetName
// copy other members from this instance
_houseNumber);
}
...
}
Это общая проблема с примерами доменного дизайна. Эксперт по доменам отсутствует, и это тот человек, который скажет вам, что такое адрес и его требования. Я подозреваю, что Эксперт по Доменам скажет вам, что у Адреса нет Точки. Возможно, вы сможете создать точку из адреса, но для этого не потребуется точка. Также почтовый ящик не будет отдельным значением в адресе. Вам может понадобиться адресный класс почтового ящика (POBoxAddress). Я говорю об этом, потому что этот класс выглядит так, как будто он был определен разработчиком, а не специалистом по отгрузке или выставлению счетов. Поговорив с экспертом домена, вы можете уменьшить количество параметров конструктора.
второй
Вы можете начать группировать параметры как объекты-значения. Вы можете создать объект стоимости города. Это может потребовать города, региона / штата и страны. Я думаю, что название города мало что значит, если я не знаю регион и страну. Сказать, что Париж означает не что иное, как Париж, Иллинойс, США или Париж, Иль-де-Франс, Франция, дает вам полную картину. Так что это также уменьшит количество параметров счетчика для объекта Address.
Если вы пойдете по пути DDD и найдете эксперта по домену, для которого вы кодируете, вы не должны быть экспертом. Иногда проблемы не должны быть исправлены с помощью кода или изящного шаблона проектирования.
immutable подходит для параллельных вычислений, без блокировок и без блокировок, immutable для высокой производительности и хорошей масштабируемости.
Таким образом, Value Object может работать лучше в параллельной системе, включать в систему распределения, заменять старый VO новым VO, нет необходимости обновлять, поэтому нет блокировки.