Инициализация объектов предметной области — соблюдение режима SOLID, скажите, не спрашивайте

Я пытаюсь следовать некоторым из наиболее современных принципов проектирования, включая SOLID и Domain Driven Design. Мой вопрос касается того, как люди обрабатывают «Инициализацию» объектов домена.

Вот простой пример:

На основе SOLID я не должен зависеть от конкрементов, поэтому создаю интерфейс и класс. Так как я пользуюсь преимуществами Domain Driven Design, я создаю объект с соответствующими методами. (то есть без анемии).

Interface IBookstoreBook
{
   string Isbn {get; set;} 
   int Inventory {get; set;}
   void AddToInventory(int numBooks);
   void RemoveFromInventory(int numBooks);
}

public class BookstoreBook : IBookstoreBook
{
   public string Isbn {get; set;} 
   public int Inventory {get; private set;}
   public void AddToInventory(int numBooks);
   public void RemoveFromInventory(int numBooks);       
}

Чтобы помочь с тестированием и быть более слабо связанным, я также использую контейнер IoC для создания этой книги. Поэтому, когда книга создается, она всегда создается пустой. Но если у книги нет ISBN и инвентаря, она недействительна.

BookstoreBook(string bookISBN, int bookInventory) {..} // Does not exist

У меня может быть 4 или 5 разных классов, использующих BookstoreBook. Для одного,

public class Bookstore : IBookstore
{
   ...
   public bool NeedToIncreaseInventory(BookstoreBook book) { ...}
   ...
}

Как любой метод узнает, что получает действительную книгу? Мои решения ниже, похоже, нарушают принцип «Говори, не спрашивай».

а) Должен ли каждый метод, использующий книжный магазин, проверяться на достоверность? (т. е. должен ли NeedToIncreaseInventory проверять действительность книг? Я не уверен, что он должен знать, что делает BookstoreBook действительным.)

б) Должен ли я иметь «CreateBook» в объекте IBookstoreBook и просто «предполагать», что клиенты знают, что они должны вызывать это в любое время, когда они хотят инициализировать BookstoreBook? Таким образом, NeedToIncreaseInventory просто будет доверять тому, что «CreateBook» уже был вызван в BookstoreBook.

Меня интересует, какой рекомендуемый подход здесь.


person ItsAllABadJoke    schedule 11.02.2017    source источник


Ответы (4)


Во-первых, я думаю, что у вашего BookstoreBook нет действительно релевантных методов, что означает, что у него нет никакого релевантного поведения, вообще нет бизнес-правил. И поскольку он не содержит никаких бизнес-правил, он фактически анемичен. У него просто куча геттеров и сеттеров. Я бы сказал, что такой метод, как AddToInventory, который просто добавляет +1 к свойству, не имеет смысла.

Кроме того, зачем вашему BookstoreBook знать, сколько представителей этого типа в вашем Bookstore? Я чувствую, что это, вероятно, то, за чем должен следить сам Bookstore.

Что касается пункта а): нет, если вы создаете книги на основе пользовательского ввода, вы должны проверить предоставленные данные, прежде чем создавать новую книгу. Это предотвратит появление в вашей системе недействительных книг.

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

person Pox    schedule 11.02.2017
comment
Спасибо за ваши мысли. Я знаю игрушечный пример, это просто - игрушка. Но вопрос по-прежнему актуален. - person ItsAllABadJoke; 12.02.2017
comment
Спасибо за ваши мысли. Я знаю игрушечный пример, это просто - игрушка. Но по-прежнему актуален вопрос — кто отвечает за добавление данных в объект предметной области? Вы говорите, что если вы создаете на основе пользовательского ввода, вы должны проверить предоставленные данные, прежде чем даже создавать ... Я согласен. Но книги можно создавать из базы данных и многих других мест. Я предполагаю, что другим приходилось решать эту проблему, если они используют DDD, и меня интересует их подход. Должны ли мы все использовать фабрики — как вы предлагаете принимать необходимые данные в качестве входных данных? - person ItsAllABadJoke; 12.02.2017

Прежде всего, это отличный способ убедиться, что состояние объекта может быть установлено только поведением (методами), чтобы сделать все установщики свойств закрытыми. Это также позволяет убедиться, что все связанные свойства установлены при изменении состояния.

Но если у книги нет ISBN и инвентаря, она недействительна.

У вас есть два бизнес-правила. Начнем с ISBN. Если книга недействительна без нее, это ДОЛЖНО быть указано в конструкторе. В противном случае вполне возможно создать недействительную книгу. ISBN также имеет определенный формат (как минимум длину). Так что этот формат тоже должен быть проверен.

Что касается инвентаря, я считаю, что это неправда. У вас могут быть книги, которые распроданы, или книги, которые можно забронировать до их выпуска. Правильно? Так что книга МОЖЕТ существовать без инвентаря, это просто маловероятно. Если вы посмотрите на взаимосвязь между инвентарем и книгами с точки зрения предметной области, они представляют собой два отдельных объекта с разными обязанностями.

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

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

Таким образом, инвентарной части приложения на самом деле не нужно знать все, что нужно знать о книге. Таким образом, я бы рекомендовал, чтобы инвентаризация знала только об идентичности книги (обычно именно так корневые агрегаты могут ссылаться друг на друга согласно книге Мартина Фаулера).

Инверсия контейнера управления обычно используется для управления службами (в DDD службами приложений и службами домена). Его работа не в том, чтобы действовать как фабрика для объектов домена. Это только усложнит ситуацию без какой-либо пользы.

person jgauffin    schedule 12.02.2017

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

Почему ваш контейнер IoC создает книги? Это немного странно. Ваша доменная модель должна быть независимой от контейнера (объединение интерфейсов и реализация — забота вашего корень композиции).

Как любой метод узнает, что получает действительную книгу?

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

Модель data знает, что создает правильную книгу, потому что метод конструктора/фабрики принимает ее аргументы, не вызывая исключения.

Должен ли каждый метод, использующий книжный магазин, проверяться на достоверность?

Нет, как только у вас появится Книга, она останется в силе (в вашей модели предметной области не должно быть никаких глаголов, которые могли бы создать недопустимую модель данных).

Должен ли я иметь «CreateBook» в объекте IBookstoreBook и просто «предполагать», что клиенты знают, что они должны вызывать это в любое время, когда они хотят инициализировать BookstoreBook? Таким образом, NeedToIncreaseInventory просто будет доверять тому, что «CreateBook» уже был вызван в BookstoreBook.

Это нормально иметь фабрику по созданию объектов. См. Эванс, глава 6.

книги могут быть созданы из базы данных и многих других мест. Я предполагаю, что другим приходилось решать эту проблему, если они используют DDD, и меня интересует их подход. Должны ли мы все использовать фабрики — как вы предлагаете принимать необходимые данные в качестве входных данных?

На самом деле есть только два источника данных — ваша собственная книга записей (в этом случае вы загружаете данные через репозиторий) и все остальное (где вам нужно убедиться, что данные соответствуют к предположениям вашей модели.

person VoiceOfUnreason    schedule 12.02.2017

На основе SOLID я не должен зависеть от конкрементов

Если вы имеете в виду принцип инверсии зависимостей, это не совсем так.

- Модули высокого уровня не должны зависеть от модулей низкого уровня. Оба должны зависеть от абстракций.

- Абстракции не должны зависеть от деталей. Детали должны зависеть от абстракций.

Ни один объект домена не находится на более высоком уровне, чем другой, и обычно ни один объект на уровне домена не является деталью, поэтому DIP обычно не применяется к объектам домена.

Я также использую контейнер IoC для создания этой книги.

Учитывая, что BookstoreBook не имеет зависимости, я не уверен, зачем вам это делать.

Как любой метод узнает, что получает действительную книгу?

Предполагая, что книга Всегда действительна, всегда последовательна. Обычно для этого требуется наличие одного конструктора Book, который проверяет все соответствующие правила во время создания, и методов изменения состояния, обеспечивающих соблюдение инвариантов в отношении Book.

a) ...

b) ...

Здесь вы смешиваете две проблемы: убедитесь, что Book находится в согласованном состоянии, где бы он ни использовался, и инициализируйте Book. Я не уверен, о чем на самом деле ваш вопрос, но если вы примените всегда действующий подход и забудете о том, что Книга является интерфейсом/абстракцией более высокого уровня, вам должно быть хорошо идти.

person guillaume31    schedule 15.02.2017