DDD: как сохранить объект комплексного значения неизменным?


Я хотел бы смоделировать Address как объект ценности. Поскольку это хорошая практика, чтобы сделать его неизменяемым, я решил не предоставлять никакого сеттера, который мог бы позволить изменить его позже.

Общий подход заключается в передаче данных конструктору; однако, когда объект value довольно большой, он может стать довольно раздутым:

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 6

3 ответа:

Главная проблема сбольшим списком параметров - это читабельность и опасность того, что вы перепутаете параметры. Вы можете решить эти проблемы с помощьюBuilder pattern , как описано вEffective 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) приведет к ошибке времени выполнения, а не к ошибке времени компиляции при использовании конструктора. Вы также должны рассмотреть возможность использования большего количества объектов Value, таких как PostalCode, например (в отличие от передачи строки).

В связи с этим, иногда бизнес-требования требуют изменения части объекта ценности. Например, при первоначальном вводе адреса номер улицы мог быть написан с ошибкой, и теперь его необходимо исправить. Поскольку вы смоделировали адрес как неизменяемый объект, то здесь нет задатчика. Одним из возможных решений этой проблемы является введение "функции без побочных эффектов" для объекта значения адреса. Функция возвращает копию самого объекта, за исключением нового названия улицы:

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

2-й
Вы можете начать группировать параметры как объекты значений. Вы можете создать объект стоимости города. Для этого могут потребоваться город, область/штат и страна. Я думаю, что название города ничего не значит, если я не знаю регион и страну. Высказывание Paris означает не что иное, как Париж, Иллинойс, США или Париж, Иль-де-Франс, фр дает вам полную картину. Таким образом, это также уменьшит количество параметров count до объекта Address.

Если вы идете по дороге DDD найти эксперта домена для домена, для которого вы кодируете, вы не должны быть экспертом. Иногда проблемы не должны быть исправлены с помощью кода или изящного Шаблона дизайна.

Immutable подходит для параллельных вычислений, без блокировки и блокировки, immutable-для высокой производительности и хорошей масштабируемости.

Таким образом, объект Value может работать лучше в параллельной системе, включать в распределенную систему, заменять старый VO новым VO, не нужно обновлять, поэтому нет блокировки.