Является ли метод инициализации запахом кода?
Сейчас я кодирую кучу систем. Они не связаны с общим интерфейсом.
Некоторые примерные системы: MusicSystem
, PhysicsSystem
, InputSystem
и т.д.
В настоящее время MusicSystem
загружает большое количество аудиофайлов в свой конструктор, и, как результат, может быть некоторое кратковременное отставание, когда объект сначала создается.
Из-за этого, должен ли этот код загружать все аудиофайлы в методе Initialize()
вместо этого? Это позволяет программисту определить, когда он хочет загрузить аудиофайлы, но затем, если он забыл позвонить Initialize()
, программа выйдет из строя.
Поскольку не всем системам нужен метод Initialize()
, программист должен просмотреть каждую систему, чтобы узнать, имеет ли класс метод Initialize()
, и если да, вызовите его. Это немного громоздко.
Какой подход предпочтительнее с точки зрения общих принципов проектирования?
Ответы
Ответ 1
Подумайте о других API, с которыми вы написали код. Когда в последний раз API требовал, чтобы программист знал, чтобы вызвать метод init
, в противном случае сбой во время выполнения?
Будучи потребителем вашего API, он будет приводить меня орехи, если я должен был знать, чтобы вызвать метод init
после создания объекта. Я бы порекомендовал альтернативу, которую я видел и использовал из первых рук: document дорогостоящий объект-экземпляр.. Какая точка отсрочки дорогостоящей инициализации, если требуется, чтобы программа не сработала?
Ответ 2
В принципе, вы пытаетесь сбалансировать эффективность программы в сравнении с эффективностью программы. Поэтому в этом случае это не обязательно запах кода, в зависимости от того, что более важно. Вы предпочли бы сделать код немного проще в использовании и сложнее сломать или быстрее загружать программу?
Однако альтернатива, которую вы могли бы попробовать, это lazy-loading.
Затем вы можете получить свой торт и съесть его тоже:
- Клиентский код не должен вызывать
Initialize
- данные не будут загружаться до тех пор, пока они не понадобятся.
Ответ 3
Перемещение тяжелой инициализации из конструктора не является запахом кода.
Однако, полагаясь на внешнего вызывающего абонента, чтобы вызвать эту инициализацию, это запах - он называется Временная связь.
Создайте метод Initialize()
для своего класса, но сделайте его private
(или protected
, если нужно).
Также напишите метод EnsureInitialized()
, который инициирует инициализацию, если требуется, но только один раз для экземпляра.
Затем каждая открытая точка входа вашего класса должна вызывать EnsureInitialized()
в начале - вы инициализируетесь, откладываясь до точки первого использования.
С этим на месте и при условии, что вам удобнее блокировки, вы можете отменить вызов EnsureInitalized()
в фоновый поток, чтобы выполнить работу в фоновом режиме, с минимальным воздействием на передний план.
Ответ 4
Я не думаю, что метод инициализации - это запах кода, но сбой (а не изящный изгиб), если пользователь не может вызвать ваш метод инициализации, точно звучит как.
Если вы убедитесь, что все ваши системы имеют метод Initialize()
(даже если он ничего не делает), вашим пользователям не нужно беспокоиться о том, следует ли его вызывать.
Ответ 5
Несколько пунктов здесь.
Прежде всего, при написании интерфейса с методом Initialize не обязательно неправильно, обычно считается, что плохая практика полагается на потребителей вашего сервиса /API для настройки какого-либо состояния самостоятельно, прежде чем использовать его предоставляемые функции.
Во-вторых, я не уверен, что вы подразумеваете под "небольшим отставанием". Если проблема заключается в том, что ваш пользовательский интерфейс теряет ответственность при загрузке, фоновая задача может быть использована для устранения отставания. Посмотрите на BackgroundWorker, например, или реализуйте какое-то другое многопоточное поведение, чтобы получить то место, где вы хотите быть.
Ответ 6
Просто идея с моей головы: добавьте bool в конструктор - нужно ли инициализировать вызывающий объект в конструкторе.
Ответ 7
Как получить инициализированный метод private/protected и вызвать инициализацию внутри, когда выполняется какой-либо метод, требующий инициализации?
например.
public class MyClass
{
private bool _isInitialized;
public MyClass()
{
... only basic initializations...
}
private void initialize()
{
if (_isInitialized)
return;
// initialize here
}
public void SimpleMethod()
{
// doesn't need to initialize
}
public void ComplexMethod()
{
initialize();
// do something...
}
}
Ответ 8
Я думаю, что хорошей идеей было бы документировать, что конкретный класс "дорог", чтобы стимулировать. Затем программист должен только инициировать объект, когда он абсолютно необходим для использования.
Ответ 9
Почему бы не иметь перегруженный конструктор с логическим значением, чтобы программисты могли указать, хотят ли они пройти дорогостоящий метод инициализации.
Тогда вам не нужно будет проверять наличие специального метода.
Ответ 10
Если аудиофайлы всегда одинаковы. Вы можете попробовать загрузить их из статического свойства. Как только объект загружает аудиофайлы. Они будут доступны всем, если список аудиофайлов хранится в статическом свойстве.
Ответ 11
Ваш конструктор может запустить фоновый поток, который выполняет загрузку. Тогда что-нибудь в классе, который должен использовать эти данные, просто проверяет, завершена ли асинхронная загрузка, а если нет, подождите.
Это ускоряет создание класса, он скрывает все детали многопоточности от ваших пользователей класса и избавляется от уродливого шаблона init.
Ответ 12
Это вовсе не "запах кода". Не совсем необычно иметь что-то подобное. Например, посмотрите на класс SqlConnection
в .NET. Существует конструктор по умолчанию, который не принимает параметров, и есть конструктор, который принимает строку соединения. Тот, который берет строку подключения, удобен, когда вы хотите подключиться к базе данных и использовать значения по умолчанию для таймаута соединения и т.д. Но если вы хотите изменить эти свойства или хотите, чтобы экземпляр подключения был готов, но контролировал больше точно когда он подключен, вы вызываете конструктор по умолчанию, задаете свойства, а затем вызываете Open
, когда будете готовы к подключению.
Ответ 13
Сначала я собирался сказать, что это не запах кода, поскольку он позволяет программисту решать, когда инициализировать объект, скажем, путем выполнения всех объектов параллельно с использованием рабочих потоков.
Но потом я подумал, что это запах кода, потому что выбор синхронной или асинхронной конструкции может быть легко реализован в самом конструкторе, возможно, аргумент конструктора, чтобы предпочесть один над другим.
И тогда я подумал, что о сценариях, где невозможно исключить исключения? Путем настройки всех данных в конструкторе невозможно указать отказ, если нет возможности исключения, отличной от аксессора, например IsInitialised
или что-то еще. (Symbian - пример ОС без поддержки исключений.)
Итак, я думаю, что вонючесть действительно зависит от среды, в которой вы работаете, и от ваших личных предпочтений.
Ответ 14
Вам нужно подумать о фрагментации вашей * System, потому что похоже, что у вас есть один супер-мастер-тип, который управляет всем миром и делает большую часть материала в конструкторе. Что не очень хорошо, выглядит вонючим и разрушает принцип SOLID.
Вы можете переместить свои длительные операции и операции ввода-вывода в специализированные обертки, которые затем можно передать в качестве параметров, таких как Stream, Connection, IDataReader и т.д. в .Net. При этом он будет более предсказуемым, если операция может потреблять много процессорных, памяти или пропускной способности ввода-вывода.
Ответ 15
Если у вас есть "MusicSystem", которая не имеет смысла (и выражает это, взорвав) без инициализации, мне трудно понять пример использования для создания экземпляра этого объекта, но не инициализировать его.
Если это вопрос передачи его с места на место, я мог бы предложить взглянуть на Generic Lazy, где вы могли бы ленить нагрузку объект.
Таким образом, вы избегаете неестественной связи, связанной с необходимостью создания экземпляра, а затем сразу же узнаете, что нужно вызвать Initialize(), но вы получите преимущества от того, чтобы не тратить средства до тех пор, пока объект не был фактически использован.
Что касается запаха кода, я лично считаю, что методы Initialize() являются запахами кода. Это не всегда указывает на то, что что-то не так, конечно, но обычно это говорит мне о том, что возможности для инверсии зависимостей упускаются. Если мне нужно что-то создать, а затем вызвать Initialize(), мне интересно, почему я не могу создать экземпляр объекта, а затем передать ему все, что ему нужно, чтобы считаться инициализированным (или почему он не требует инициализации Foo в его конструктор).
Ответ 16
Конечно нет. Согласно руководящим принципам MSDN, конструктор должен выполнять минимальную работу. Поэтому такие вещи, как выделение ресурсов или инициализация тяжелых объектов, должны быть размещены где-то в другом месте, например Initialize().
https://msdn.microsoft.com/en-us/library/ms229060(v=vs.110).aspx
Конструкторы не должны много работать, кроме захвата параметров конструктора. Стоимость любой другой обработки должна быть отложена до тех пор, пока не потребуется.