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, нет необходимости обновлять, поэтому нет блокировки.

Другие вопросы по тегам