Подтвердить что ты не робот

Сколько работы должно быть сделано в конструкторе?

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

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

Какое изящное решение для этого?

4b9b3361

Ответ 1

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

Например, скажем, мне нужно отобразить следующий класс компании в представлении деталей:

public class Company
{
    public int Company_ID { get; set; }
    public string CompanyName { get; set; }
    public Address MailingAddress { get; set; }
    public Phones CompanyPhones { get; set; }
    public Contact ContactPerson { get; set; }
}

Поскольку я хочу отобразить всю информацию о компании в представлении подробностей, мой конструктор будет содержать весь код, необходимый для заполнения каждого свойства. Учитывая, что это сложный тип, конструктор компании также инициирует выполнение конструктора Address, Phones and Contact.

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

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

Ответ 2

Подводя итог:

  • Как минимум, ваш конструктор должен настроить объект таким образом, чтобы его инварианты были истинными.

  • Ваш выбор инвариантов может повлиять на ваших клиентов. (Объект обещает быть готовым к доступу в любое время? Или только в определенных состояниях?) Конструктор, который заботится обо всех настройках -front может сделать жизнь проще для клиентов класса.

  • Долгосрочные конструкторы не являются по своей сути плохими, но могут быть плохими в некоторых контекстах.

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

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

  • В целом, это зависит.

Ответ 3

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

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

Ответ 4

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

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

Ответ 5

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

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

Ответ 6

Наиболее важными заданиями конструктора являются предоставление объекту начального действительного состояния. Наиболее важным ожиданием конструктора, на мой взгляд, является то, что конструктор не должен иметь НИКАКИХ ПОБОЧНЫХ ЭФФЕКТОВ.

Ответ 7

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

Ответ 8

Я бы согласился с тем, что длинные конструкторы не являются по своей сути плохими. Но я бы сказал, что твои дела почти всегда ошибаются. Мой совет похож на тот, что у Hugo, Rich и Litb:

  • сохраните работу, которую вы выполняете в конструкторах, до минимума - продолжайте фокусироваться на состоянии инициализации.
  • Не бросайте из конструкторов, если вы не можете этого избежать. Я пытаюсь бросить только std:: bad_alloc.
  • Не называйте API OS или библиотеки, если вы не знаете, что они делают - большинство может блокировать. Они быстро запускаются на вашем блоке dev и тестовых машинах, но в поле они могут быть заблокированы в течение длительных периодов времени, когда система занята чем-то другим.
  • Никогда, никогда не делайте ввода-вывода в конструкторе - любого типа. Входы/выходы обычно подпадают под все виды очень длинных латентных значений (от 100 миллисекунд до нескольких секунд). Ввод-вывод включает
    • Дисковый ввод-вывод
    • Все, что использует сеть (даже косвенно). Помните, что большинство ресурсов могут быть недоступны.

Пример проблемы ввода-вывода: у многих жестких дисков есть проблема, когда они попадают в состояние, когда они не обслуживают чтение или запись за 100 или даже тысячи миллисекунд. Первые и поколения твердотельных дисков делают это часто. У пользователя теперь есть возможность узнать, что ваша программа jus висела немного - они просто думают, что это ваше плохое программное обеспечение.

Конечно, злобность длинного конструктора зависит от двух вещей:

  • Что означает "long" означает
  • Как часто в заданный период создаются объекты с "длинными" конструкторами.

Теперь, если "long" - это всего несколько 100 дополнительных тактовых циклов работы, то это не очень долго. Но конструктор попадает в 100 микросекундных диапазонов, я полагаю, что он довольно длинный. Конечно, если вы только создаете экземпляр одного из них или создаете их редко (скажем, каждые несколько секунд), вы вряд ли увидите проблемы из-за продолжительности в этом диапазоне.

Частота - важный фактор, 500 долларов США не проблема, если вы только строите несколько из них: но создание миллиона из них будет представлять значительную проблему с производительностью.

Давайте поговорим о вашем примере: заполнение дерева объектов каталога внутри объекта "Каталог классов". (заметьте, я собираюсь предположить, что это программа с графическим интерфейсом). Здесь ваша продолжительность CTOR не зависит от кода, который вы пишете, - его ответчика на время, необходимое для перечисления произвольно большого дерева каталогов. Это плохо на локальном жестком диске. Это еще более проблематично для удаленного (сетевого) восстановления.

Теперь, представьте, сделайте это в потоке пользовательского интерфейса - ваш пользовательский интерфейс остановится на своих треках в течение секунд, 10 секунд или даже минут. В Windows мы называем это зависанием пользовательского интерфейса. Они плохие плохие (да, у нас есть... да мы много работаем, чтобы их устранить).

UI Hangs - это то, что может заставить людей действительно ненавидеть ваше программное обеспечение.

Правильная вещь здесь - это просто инициализировать объекты вашего каталога. Создайте дерево каталогов в цикле, который можно отменить, и удерживает ваш пользовательский интерфейс в реагирующем состоянии (кнопка отмены всегда должна работать)

Ответ 9

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

На вашем объекте структуры каталогов. Недавно я реализовал браузер samba (windows share) для моего HTPC, и поскольку это было невероятно медленным, я решил фактически инициализировать каталог, когда он был затронут. например Сначала дерево состояло бы из всего списка машин, тогда всякий раз, когда вы просматриваете каталог, система автоматически инициализирует дерево с этой машины и получает каталог с одним уровнем глубже и т.д.

В идеале, я думаю, вы могли бы даже принять его до написания рабочего потока, который сначала проверяет ширину каталогов, и будет отдавать приоритет каталогу, который вы просматриваете в данный момент, но обычно это слишком много работает для чего-то простого; )

Ответ 10

RAII является основой управления ресурсами С++, поэтому приобретайте ресурсы, которые вам нужны в конструкторе, выпустите их в деструкторе.

Это когда вы устанавливаете свои инварианты класса. Если это требует времени, требуется время. Чем меньше конструкций "if X exists do Y" у вас есть, тем проще будет дизайн остальных классов. Позже, если профилирование показывает, что это проблема, рассмотрите оптимизации, такие как ленивая инициализация (получение ресурсов, когда они вам в первую очередь нужны).

Ответ 11

Сколько нужно и не больше.

Конструктор должен поместить объект в пригодное для использования состояние, следовательно, как минимум ваши переменные класса должны быть недействительными. Какие устоявшиеся средства могут иметь широкую интерпретацию. Вот надуманный пример. Представьте, что у вас есть класс, который несет ответственность за предоставление N! к вашему вызывающему приложению.

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

Другим способом его реализации было бы иметь переменную класса, которая является массивом. Конструктор установил бы все значения равными -1, чтобы указать, что значение еще не рассчитано. функция-член будет делать ленивую оценку. Он смотрит на элемент массива. Если он равен -1, он вычисляет его и сохраняет его и возвращает значение, иначе он просто возвращает значение из массива.

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

Другим способом его реализации было бы сохранить значения в текстовом файле и использовать N в качестве основы для смещения в файл, чтобы извлечь значение из. В этом случае конструктор откроет файл, и деструктор закроет файл, в то время как метод выполнит какой-то fseek/fread и вернет значение.

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

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

Ответ 12

Убедитесь, что ctor не делает ничего, что могло бы вызвать исключение.

Ответ 13

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

Ответ 14

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

Ответ 15

Я проголосовал за тонкие конструкторы и добавил в этот случай дополнительное "неинициализированное" поведение состояния.

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

Может быть трудно поймать ошибки таких объектов, если они становятся статическими, потому что конструктор запускается до main() и сложнее отследить отладчик.

Ответ 16

Отличный вопрос: пример, который вы указали, где объект "Directory" имеет ссылки на другие объекты "Directory", также является отличным примером.

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

В противном случае существует еще одна потенциальная проблема - помимо просто производительности - это объем памяти: если вы в конечном итоге сделаете очень глубокие рекурсивные вызовы, вы, скорее всего, также столкнетесь с проблемами памяти [поскольку в стеке будут храниться копии всех локальные переменные до завершения рекурсии].

Ответ 17

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

Ответ 18

Массивы объектов всегда будут использовать конструктор по умолчанию (без аргументов). Ничего подобного.

Существуют "специальные" конструкторы: конструктор копирования и оператор =().

У вас может быть много конструкторов! Или закончите с большим количеством конструкторов позже. Время от времени Билл на ла-ла-земле хочет нового конструктора с плавающими, а не двойными, чтобы сохранить эти 4 паршивых байта. (Купите RAM!)

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

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

.

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

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

В зависимости от обстоятельств, initialize() может быть закрытым. Или он может быть общедоступным и поддерживать множественные вызовы (например, повторная инициализация).

.

В конечном счете, выбор здесь варьируется в зависимости от ситуации. Мы должны быть гибкими и рассматривать компромиссы. Нет единого размера.

Подход, который мы будем использовать для реализации класса с одиночным одиночным экземпляром, использующим потоки, чтобы поговорить с частью выделенного оборудования и который должен быть написан за 1/2 часа, не обязательно является тем, что мы будем использовать для реализации класса, представляющего математику для чисел с плавающей запятой переменной точности, написанных в течение многих месяцев.