Плохо ли делать в конструкторах?
Делать все поля 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 заключается в том, что вы обнаружите ошибки, такие как недопустимые изображения, в будущем, а не в конструкторе. Вы всегда можете выполнить простые проверки для себя, чтобы до некоторой степени предварительно проверить ввод (например, проверить правильный пароль).