Хорошая практика для проверки неизменных значений объектов - PullRequest
4 голосов
/ 19 марта 2012

Предположим, класс MailConfiguration указывает параметры для отправки писем:

public class MailConfiguration {

  private AddressesPart addressesPart;

  private String subject;

  private FilesAttachments filesAttachments;

  private String bodyPart;

  public MailConfiguration(AddressesPart addressesPart, String subject, FilesAttachments filesAttachements,
    String bodyPart) {
    Validate.notNull(addressesPart, "addressesPart must not be null");
    Validate.notNull(subject, "subject must not be null");
    Validate.notNull(filesAttachments, "filesAttachments must not be null");
    Validate.notNull(bodyPart, "bodyPart must not be null");
    this.addressesPart = addressesPart;
    this.subject = subject;
    this.filesAttachements = filesAttachements;
    this.bodyPart = bodyPart;
  }
  // ...  some useful getters ......

}

Итак, я использую два объекта значений: AddressesPart и FilesAttachment.

Тезисов дваОбъекты значений имеют схожую структуру, поэтому я собираюсь раскрыть здесь только AddressesPart:

public class AddressesPart {

  private final String senderAddress;

  private final Set recipientToMailAddresses;

  private final Set recipientCCMailAdresses;

  public AddressesPart(String senderAddress, Set recipientToMailAddresses, Set recipientCCMailAdresses) {
    validate(senderAddress, recipientToMailAddresses, recipientCCMailAdresses);
    this.senderAddress = senderAddress;
    this.recipientToMailAddresses = recipientToMailAddresses;
    this.recipientCCMailAdresses = recipientCCMailAdresses;
  }

  private void validate(String senderAddress, Set recipientToMailAddresses, Set recipientCCMailAdresses) {
    AddressValidator addressValidator = new AddressValidator();
    addressValidator.validate(senderAddress);
    addressValidator.validate(recipientToMailAddresses);
    addressValidator.validate(recipientCCMailAdresses);
  }

  public String getSenderAddress() {
    return senderAddress;
  }

  public Set getRecipientToMailAddresses() {
    return recipientToMailAddresses;
  }

  public Set getRecipientCCMailAdresses() {
    return recipientCCMailAdresses;
  }

}

и связанный валидатор: AddressValidator

public class AddressValidator {

  private static final String EMAIL_PATTERN
    = "^[_A-Za-z0-9-]+(\\.[_A-Za-z0-9-]+)*@[A-Za-z0-9]+(\\.[A-Za-z0-9]+)*(\\.[A-Za-z]{2,})$";

  public void validate(String address) {
    validate(Collections.singleton(address));
  }

  public void validate(Set addresses) {
    Validate.notNull(addresses, "List of mail addresses must not be null");
    for (Iterator it = addresses.iterator(); it.hasNext(); ) {
      String address = (String) it.next();
      Validate.isTrue(address != null && isAddressWellFormed(address), "Invalid Mail address " + address);
    }
  }

  private boolean isAddressWellFormed(String address) {
    Pattern emailPattern = Pattern.compile(EMAIL_PATTERN);
    Matcher matcher = emailPattern.matcher(address);
    return matcher.matches();
  }
}

Таким образом, у меня естьдва вопроса:

1) Если по каким-то причинам позже мы захотим по-разному проверить адрес электронной почты (например, включить / исключить некоторые псевдонимы, соответствующие существующему mailingList), я должен представить вид IValidator какпараметр конструктора?как следующее, а не конкретная зависимость (как я сделал):

public AddressValidator(IValidator myValidator) {
   this.validator = myValidator;
}

Действительно, это будет соответствовать принципу D принципа SOLID: Внедрение зависимостей.

Однако, если мы будем следовать этомулогично, будет ли большинство объектов Values ​​иметь абстрактный валидатор, или большую часть времени это просто излишество (подумаете о YAGNI?)?

2) Я читал в некоторых статьях, чем в отношении DDD, все проверки должны присутствовать и присутствовать только в Aggregate Root, в данном случае это означает: MailConfiguration.

Прав ли я, еслиЯ считаю, что неизменяемые объекты никогда не должны находиться в неклейком состоянии?Таким образом, будет ли проверка в конструкторе, как я сделал, предпочтительнее в соответствующей сущности (и, таким образом, избегать агрегирования, чтобы беспокоиться о проверке его «потомков»?

Ответы [ 4 ]

4 голосов
/ 20 марта 2012

В DDD есть базовый шаблон, который отлично справляется с проверкой и сборкой объектов для создания нового: Factory .

Я прочитал в некоторых статьях, чем в отношении DDD, все проверки должен присутствовать и присутствовать только в совокупном корне

Я категорически не согласен с этим. В DDD может быть логика валидации в самых разных местах:

  • Проверка при создании, выполненная Фабрикой
  • Исполнение инвариантов совокупности, обычно выполняемое в корне совокупности
  • В доменных службах можно найти валидацию, охватывающую несколько объектов.
  • и т.д.

Кроме того, я нахожу забавным, что вы удосужились создать объект значения AddressesPart - что хорошо, не задумываясь о том, чтобы в первую очередь сделать EMailAddress объектом значения. Я думаю, что это немного усложняет ваш код, потому что нет инкапсулированного представления о том, что такое адрес электронной почты, поэтому AddressesPart (и любой объект, который будет манипулировать адресами в этом отношении) вынужден иметь дело с AddressValidator, чтобы выполнить проверку его адресов. Я думаю, что это должна быть не его ответственность, а ответственность AddressFactory.

2 голосов
/ 19 марта 2012

Я не совсем уверен, буду ли я следовать за вами на 100%, но один из способов обеспечить, что неизменяемые объекты можно создавать, только если они действительны, - это использовать Essence Pattern .

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

Принципы SOLID и хороший DDD соблюдаются, посколькуродительский неизменяемый класс все еще делает только одно, но позволяет другим создавать его через его «сущность».

В качестве примера, посмотрите расширение Ldap для SpringБиблиотека безопасности.

1 голос
/ 20 марта 2012

Сначала несколько наблюдений.

Почему нет дженериков?J2SE5.0 вышел в 2004 году.

Текущая версия Java SE имеет Objects.requiresNonNull в качестве стандарта.Немного глотка и заглавная буква невернаТакже возвращает переданный объект, поэтому не нуждается в отдельной строке.

    this.senderAddress = requiresNonNull(senderAddress);

Ваши классы не совсем неизменны.Они подклассы.Также они не делают безопасную копию своих изменяемых аргументов (Set s - позор, в библиотеке Java пока нет неизменяемых типов коллекций).Примечание: скопируйте перед проверкой.

    this.recipientToMailAddresses = validate(new HashSet<String>(
        recipientToMailAddresses
    ));

Использование ^ и $ в регулярном выражении немного вводит в заблуждение.

Если проверка правильности меняется, то есть два очевидных (вменяемых) выбор:

  • Только самые широкие вариации в этом классе.Проверяйте более конкретно в контексте, который будет использоваться.
  • Передайте используемый валидатор и укажите это как свойство.Чтобы быть полезным, клиентский код должен был бы проверить и сделать что-то разумное с этой информацией, что маловероятно.

Не имеет большого смысла передавать валидатор в конструктор и затем отбрасыватьЭто.Это делает конструктор слишком сложным.Если необходимо, поместите его в статический метод.

Включающий экземпляр должен проверить, что его аргумент действителен для этого конкретного использования, но не должен перекрываться с классами, гарантирующими, что они в целом допустимы.Где бы это закончилось?

0 голосов
/ 07 декабря 2016

Хотя это старый вопрос, но для любого, кто наткнулся на предмет, пожалуйста, оставьте его простым с POJO (Plain Old Java Objects). Что касается проверок, то здесь нет единой истины, потому что для чистого DDD необходимо всегда помнить о контексте. Например, пользователь без данных кредитной карты может и должен иметь возможность создать учетную запись. Но данные кредитной карты необходимы при оформлении покупки на странице корзины покупок. Как красиво это решается с помощью DDD, перемещая кусочки кода в сущности и объекты-значения, к которым он естественным образом принадлежит.

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

Более того, Address в качестве отдельного объекта значения сам по себе ничего не передает по сравнению с ShippingAddress, HomeAddress или CurrentResidentialAddress ... вездесущим языком, другими словами, имена передают свое намерение.

...