Плохо ли делать в конструкторах?

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

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

С другой стороны, это немного странно. Более того, в этом выступлении около 17:58 есть веские причины не делать много работы в конструкторе. Я думаю, что смогу устранить проблему, передав соответствующие аргументы в качестве аргументов конструктора.

Остается вопрос: плохо ли работает много (или даже вся) работа в конструкторах?

5 ответов

Решение

Я думаю, что "Делать работу в конструкторе" это нормально...

... до тех пор, пока вы не нарушите принцип единой ответственности (SRP) и не будете использовать Dependency Injection (DI).

Я задавал себе этот вопрос слишком поздно. И мотивация против выполнения работы в конструкторе, который я нашел:

  • Это затрудняет тестирование
    • Все примеры, которые я видел, были, где DI не использовался. Это не было фактически ошибкой конструктора, делающего фактическую работу.
  • Вам могут не понадобиться все результаты, которые вычисляет ваш конструктор, тратя впустую время обработки, и это трудно проверить изолированно.
    • Это в основном нарушение SRP, а не ошибка конструктора, скажем так.
  • Старые компиляторы имели / имели проблемы с исключениями, генерируемыми в конструкторах, поэтому вы не должны делать ничего, кроме назначения полей в конструкторах.
    • Я не думаю, что это хорошая идея - писать новый код с учетом исторических недостатков компилятора. С таким же успехом мы могли бы покончить с C++11 и всем, что хорошо, если мы это сделаем.

Мое мнение таково...

... если ваш конструктор должен выполнить работу, чтобы придерживаться http://en.wikipedia.org/wiki/Resource_Acquisition_Is_Initialization и класс не нарушает SRP и DI используется правильно; Тогда делать работу в конструкторе - хорошо! Вы даже можете выдать исключение, если хотите предотвратить использование объекта класса, инициализация которого завершилась неудачно, вместо того, чтобы полагаться на то, что пользователь проверяет какое-либо возвращаемое значение.

Это очень открытый вопрос, поэтому мой ответ будет максимально общим...

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

С учетом сказанного вы заметите, что он также затрагивает внедрение зависимостей / шаблон провайдера, который известен своими усложняющими конструкторами. В таком случае предпочтительнее оставлять в конструкторе ТОЛЬКО код провайдера /DI. Опять же, ответ зависит от того, какие шаблоны вы используете и как ваш код "совмещается".

Весь смысл использования конструктора заключается в создании аккуратного объекта, который можно использовать немедленно; т.е. new Student("David Titarenco", "Senior", 3.5), Там нет необходимости делать david.initialize() как это было бы совершенно глупо.

Вот, например, мой производственный код:

    Config Conf = new Config();
    Log.info("Loading server.conf");
    Conf.doConfig();

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

Я думаю, что это полностью зависит от вашего кода, и вы не должны думать, что помещать "вещи" в ваш конструктор - это плохо. Для этого и нужны конструкторы! Иногда мы увлекаемся ООП (getThis, setThat, doBark) когда действительно все, что нужно сделать классу, это загрузить файл конфигурации. В таких случаях просто поместите все в конструктор и назовите это день!

Когда я поместил слишком много кода в конструктор, я столкнулся со следующими проблемами:

  • Было трудно написать модульные тесты для других методов этого класса, потому что он хотел сделать много вещей в конструкторе, таким образом, мне пришлось установить много допустимых вещей или, по крайней мере, макетов (БД, файл, что угодно) для самых простых юнит-тестов.
  • Было трудно написать модульные тесты для самого конструктора. В любом случае, размещение большого количества кода с разной ответственностью в одном блоке - даже плохая идея. ( Принцип единой ответственности.)
  • По первым причинам было трудно использовать этот класс. Например, это полностью помешало мне реализовать некоторые отложенные методы init, потому что в момент вызова конструктора требовалось все. Хорошо, я мог бы написать ленивый метод init в конструкторе, хорошо.
  • Рано или поздно я понял, что имеет смысл повторно использовать некоторые части кода, которые помещаются в конструктор. Ну, когда я впервые написал конструктор, я также подумал, что эти части кода будут использоваться там и всегда.
  • Когда я хотел расширить этот класс и вставить некоторую логику до или в логику конструктора супер, это просто не сработало, потому что первое, что нужно сделать в конструкторе расширенного класса, - это вызвать супер.

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

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

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

Как только вы начнете использовать Builders и Factories для создания ваших объектов, они смогут проверить предварительные и последующие условия и убедиться, что клиенты вашего кода смогут получить доступ только к полностью инициализированному объекту, а не к полуфабрикату, вы даже можете использовать в настоящее время в моде беглые интерфейсы, чтобы создать свой объект и сделать его классным;D

new EmailMessage()
    .from("demo@guilhermechapiewski.com")
    .to("destination@address.com")
    .withSubject("Fluent Mail API")
    .withBody("Demo message")
    .send();

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

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

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

Другой пример: допустим, у вас есть представление, которое загружает 20 изображений. Но только 5 показаны, и конструктор берет массив изображений для показа. Вы можете загрузить их все в конструкторе, но с точки зрения пользователя это поначалу будет казаться медленным. Или вы можете загрузить 1 "загрузочную" картинку и загрузить 1 картинку за раз. И по мере того, как пользователь прокручивает, загружает больше изображений, как показано / необходимо.

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

Другие вопросы по тегам