Построение (несколько) сложного объекта

Когда я создаю классы, простые конструкторы имеют тенденцию быть нормой. В одном из моих текущих проектов, библиотеке фильмов, у меня есть объект домена Movie . Он имеет ряд свойств, в результате чего конструктор выглядит следующим образом:

public Movie(string title, int year, Genre genre, int length, IEnumerable<string> actors)
{
    _title = title;
    _year = year;
    _genre = genre;
    _length = length;
    _actors = new List<string>(actors);
}

Это не страшно, но и не просто. Стоит ли использовать фабричный метод ( static Movie CreateMovie (...) ) или, возможно, конструктор объектов? Есть ли типичный шаблон для создания экземпляров классов доменов?

ОБНОВЛЕНИЕ: спасибо за ответы. Я, вероятно, сначала задумался над этим вопросом, хотя я узнал несколько вещей, которые будут полезны в более сложных ситуациях. Мое решение теперь состоит в том, чтобы заголовок был единственным обязательным параметром, а остальные - именованными / необязательными параметрами. Кажется, это идеальный способ для создания этого предметного объекта.

8
задан Grant Palin 27 August 2010 в 17:42
поделиться

8 ответов

Если вы используете .NET 4.0, вы можете использовать необязательные / именованные параметры , чтобы упростить создание объекта, который принимает несколько аргументов, некоторые из которых по желанию.Это полезно, когда вы хотите избежать множества различных перегрузок для предоставления необходимой информации об объекте.

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

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

4
ответ дан 5 December 2019 в 10:38
поделиться

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

Как и другие шаблоны создания, он решает проблему создания объектов (продуктов) без указания точного класса объекта, который будет создан. Шаблон проектирования фабричного метода решает эту проблему, определяя отдельный метод для создания объектов, подклассы которого могут затем переопределить, чтобы указать производный тип продукта, который будет создан.

Таким образом, шаблон фабричного метода имеет смысл, если вы хотите вернуть подклассы из Movie . Если это не является (и не будет) требованием, замена общедоступного конструктора фабричным методом не имеет никакого смысла.

В соответствии с требованиями, изложенными в вашем вопросе, ваше решение мне очень нравится: все обязательные поля передаются в конструктор как параметры. Если ни одно из ваших полей не является обязательным, вы можете добавить инициализатор по умолчанию и использовать синтаксис инициализатора объекта C # .

4
ответ дан 5 December 2019 в 10:38
поделиться

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

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

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

1
ответ дан 5 December 2019 в 10:38
поделиться

Как по мне - все зависит от модели вашего домена. Если ваша модель предметной области позволяет создавать простые объекты - вам следует это сделать.

Но часто у нас много составных объектов, и создание каждого по отдельности слишком сложно. Вот почему мы ищем лучший способ инкапсулировать логику создания составного объекта. Фактически, у нас есть только две описанные выше альтернативы - «Заводской метод» и «Конструктор объектов». Создание объекта с помощью статического метода выглядит немного странно, потому что мы помещаем в объект логику создания объекта. Конструктор объектов, в свою очередь, выглядит сложным.

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

0
ответ дан 5 December 2019 в 10:38
поделиться

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

1
ответ дан 5 December 2019 в 10:38
поделиться

Это вполне приемлемо, ИМХО. Я знаю, что статические методы иногда не одобряются, но я обычно помещаю этот код в статический метод, который возвращает экземпляр класса. Обычно я делаю это только для объектов, которым разрешено иметь нулевые значения.

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

1
ответ дан 5 December 2019 в 10:38
поделиться

Я не вижу ничего плохого в интерфейсе вашего конструктора и не вижу, что даст вам статический метод. У меня будут точно такие же параметры, верно?

Параметры не кажутся необязательными, поэтому нет способа обеспечить перегрузку с меньшим или использовать необязательные параметры.

С точки зрения вызывающей стороны это выглядит примерно так:

 Movie m = new Movie("Inception", 2010, Genre.Drama, 150, actors);

Цель фабрики — предоставить вам настраиваемый конкретный экземпляр интерфейса, а не просто вызвать для вас конструктор. Идея состоит в том, что конкретный класс не является жестко закодированным в момент создания. Так действительно лучше?

 Movie m = Movie.Create("Inception", 2010, Genre.Drama, 150, actors);

Мне кажется, что то же самое. Единственное, что лучше, это если бы Create() возвращал другие конкретные классы, чем Movie.

Одна вещь, о которой следует подумать, это как улучшить это так, чтобы вызывающий код был простым для понимания. Наиболее очевидная проблема для меня заключается в том, что непонятно, что означает число 150, не глядя на код Movie.Есть несколько способов улучшить это, если вы хотите:

  1. Используйте тип для продолжительности фильма и создайте этот тип, встроенный в new MovieLength(150)
  2. Используйте именованные параметры, если вы используете .NET 4.0
  3. (см. ответ @Heinzi) используйте инициализаторы объектов
  4. Используйте свободный интерфейс

С плавным интерфейсом ваш вызов будет выглядеть так

 Movie m = new Movie("Inception").
   MadeIn(2010).
   InGenre(Genre.Drama).
   WithRuntimeLength(150).
   WithActors(actors);

Честно говоря, все это кажется излишним для вашего случая. Именованные параметры разумны, если вы используете .NET 4.0, потому что они не намного больше кода и улучшат код в вызывающем объекте.

1
ответ дан 5 December 2019 в 10:38
поделиться

В зависимости от обстоятельств.

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

Упомянутый вами метод CreateMovie() — это еще один вариант на тот случай, если вам нужно отделить внутренний конструктор от действия по созданию экземпляра Movie.

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

3
ответ дан 5 December 2019 в 10:38
поделиться
Другие вопросы по тегам:

Похожие вопросы: