Конструктор: полноценный или минимальный? - PullRequest
2 голосов
/ 22 декабря 2009

При разработке классов вам обычно приходится выбирать между:

  • предоставляет «полный» конструктор, который принимает в качестве аргументов начальные значения для всех обязательных полей: неудобно в использовании, но гарантирует полностью инициализированные и действительные объекты
  • предоставляет только конструктор и методы доступа по умолчанию для всех необходимых полей: иногда может быть удобно, но не гарантирует правильной инициализации всех членов перед вызовом некоторых критических методов.
  • смешанный подход (больше кода, больше работы, может устранить проблему "не полностью инициализированной")

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

Ответы [ 11 ]

7 голосов
/ 22 декабря 2009

Краткий ответ: объект должен быть полностью инициализирован после вызова его конструктора.

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

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

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

Конструктор:

x = new X(a, b);

сеттер:

x = new X();
x.setA(a);
x.setB(b);

Builder:

builder = new Builder();
builder.setA(a);
builder.setB(b);
x = builder.build();

Статический фабричный метод:

x = X.newX(a, b);

Все четыре подхода создадут экземпляр x класса X.

Конструктор

Плюсы:

  • Simple

Минусы:

  • Может быть невозможно из-за ограничений
  • Может потребоваться слишком много аргументов конструктора (здесь могут помочь именованные аргументы и значения по умолчанию, если языки их поддерживают: new X(a = "a", b = "c"))

сеттер

Плюсы:

  • Умеренная сложность
  • Может быть предписано рамками

Минусы:

  • Экземпляры не могут быть полностью инициализированы

Builder

Плюсы:

  • Самый гибкий подход
  • Может повторно использовать экземпляры (используя внутренние синглтоны, мухи и кэши)

Минусы:

  • Самый сложный для реализации
  • Может генерировать исключения во время выполнения для недействительных инициализированных объектов
  • Накладные расходы экземпляра объекта компоновщика

Статический заводской метод

Плюсы:

  • Умеренная сложность
  • Может повторно использовать экземпляры (используя внутренние синглтоны, мухи и кэши)

Минусы:

  • Более сложный для реализации и использования, чем конструктор
3 голосов
/ 22 декабря 2009

Определенно "полный" во всех случаях, кроме самых сложных краевых случаев.

Для меня очень просто, что точка конструктора состоит в том, чтобы настроить объект так, чтобы он был готов к использованию. Очевидно, что неизменяемые объекты являются безусловно лучшими, но для некоторых типов объектов также часто допустимо изменять состояние позднее. Однако следует избегать использования объекта, который может быть создан в одной точке, но для его использования необходимо вызвать метод init() или setup(). Это раздражает, сбивает с толку - и какой смысл создавать объект, если незаконно вызывать «реальные» методы на нем?

На мой взгляд, нет существенных недостатков в требовании «полной» конструкции; вызывающие стороны должны были бы объединить все необходимые аргументы вместе, прежде чем они смогут использовать объект в любом случае, и заставить их это сделать в конструкторе, удаляя целый класс ошибок. Приятно знать, что если вы передадите экземпляр объекта, то будет действительным для использования, а не с некоторой временной зависимостью и / или состоянием состояния. Во всяком случае, это также может сделать более понятным для вызова кода именно те зависимости, которые необходимо собрать, если все они определены в одном чистом месте.

На самом деле единственный конкретный аргумент, который я вижу, не делая этого, - это круговые зависимости; если для класса A требуется B и наоборот, то одно или другое из этих отношений должно быть выполнено с помощью метода установки. Представляет ли это хороший дизайн , оставлено в качестве упражнения для читателя. : -)

2 голосов
/ 22 декабря 2009

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

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

Так что это голосование за смешанный подход с перегруженными конструкторами. Но наиболее важным для меня является то, что созданный объект должен быть допустимым после создания, или будет выдано исключение, в противном случае он слишком подвержен ошибкам, чтобы разрешить «не полностью инициализированные» или недопустимые объекты.

2 голосов
/ 22 декабря 2009

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

1 голос
/ 22 декабря 2009

Ребята из Qt Software хорошо обдумали эти вопросы: http://qt.gitorious.org/qt/pages/ApiDesignPrinciples

1 голос
/ 22 декабря 2009

Нет. (Вы не должны использовать одно или другое - вы должны использовать одно, другое ИЛИ смешанный подход:)

Вопрос в том, когда вы должны использовать каждый из них?

1) Есть ли несколько четко определенных вариантов использования? Или миллионы?

Пример A: объект Address, для которого всегда нужно инициализировать определенные поля. Используйте полноценный конструктор.

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

Пример В. Графический объект, имеющий миллионы параметров, которые могут быть или не быть установлены, и вызывается по-разному каждой программой, которая его использует. Используйте полный конструктор для некоторых обязательных полей (если они есть), но в основном полагайтесь на методы / свойства для установки полей.

1 голос
/ 22 декабря 2009

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

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

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

Наконец, если ваши классы часто будут разделяться на подклассы пользователем, вы можете вообще избежать аргументов конструктора. (говорит из фона PHP, где у нас нет перегрузки методов).

1 голос
/ 22 декабря 2009

Мои мысли:

  • Более 4-5 аргументов конструктора имеют тенденцию быть запутанными, трудно читаемыми и иным образом недоступными;
  • Неизменяемые объекты должны быть предпочтительнее изменяемых объектов. Это часто требует больших конструкторов;
  • Если необходимо, создайте объекты «строителя» (используя свободно распространяемые интерфейсы), которые являются изменяемыми, но в конце выдают неизменный объект;
  • Если ваш язык поддерживает это, анонимные объекты (как в Javascript) являются отличным способом предоставления множества аргументов конструктору (или любой другой функции).
0 голосов
/ 22 декабря 2009

Полный конструктор, если по какой-то причине вы не пытаетесь избежать использования исключений в вашей кодовой базе.

Я не буду вдаваться в аргументы за и против запрета всех исключений, но, например, в C ++, если вы не везде используете nothrow new, то вы не избегаете всех исключений, поэтому специальный случай не делает ' т применяется.

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

1) Установите флажок «сбой» на объекте, если конструкция не удалась, и вызывающие стороны должны проверить это (либо явно, либо это проверено в других функциях, которые ведут себя по-разному в зависимости от того, установлен ли он). Потоки файлов C ++ делают это с помощью конструктора, который принимает имя файла. Это неприятно для вызывающего, но меньше неприятностей, чем двухэтапное строительство.

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

MyObj(a,b,c); // might throw

с

MyArgs args(a,b,c);
// optionally, if caller doesn't want an exception
if (!args.ok()) handle_the_error();
MyObj(args);

Затем в конструкторе, который принимает MyArgs, MyObj также может вызвать args.ok() и вызвать исключение, если это не так. Один класс может предложить оба конструктора (или фабричные методы, если ваш язык не позволяет использовать несколько конструкторов), и позволить вызывающей стороне решить.

По сути, если вы хотите избежать исключения, то вызывающему абоненту придется где-то вручную проверять успешность. Лично я считаю, что если причина неудачи сводится к плохим аргументам, то лучше проверить заранее. Очевидно, что файловые потоки не могут этого сделать, потому что, является ли аргумент «плохим» или нет, может измениться, если файловая система изменяется между проверкой и конструктором. Таким образом, у вас нет выбора, кроме как проверить потом. Это все же лучше, чем двухэтапное строительство.

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

0 голосов
/ 22 декабря 2009

«Использование конструктора для заполнения полей данных» - это более быстрый способ, чем «Конструктор по умолчанию + назначение данных полям вручную»

Но всегда помните, что в качестве хорошей практики метод (также конструктор) не должен иметь более 6-7 параметров.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...