Есть ли когда-нибудь причина выполнять всю работу с объектом в конструкторе?

49

Позвольте мне предвосхитить это, сказав, что это не мой код или код моих коллег. Несколько лет назад, когда наша компания была меньше, у нас было несколько проектов, в которых мы нуждались, которых у нас не было, поэтому они были переданы на аутсорсинг. Теперь я ничего не имею против аутсорсинга или подрядчиков в целом, но кодовая база, которую они создали, - это масса WTF. При этом, это (в основном) работает, так что я полагаю, что он входит в топ-10% аутсорсинговых проектов, которые я видел.

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

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

Например, (код на Java, если это имеет значение, но я надеюсь, что это будет более общий вопрос):

public class Foo {
  private int bar;
  private String baz;

  public Foo(File f) {
    execute(f);
  }

  private void execute(File f) {
     // FTP the file to some hardcoded location, 
     // or parse the file and commit to the database, or whatever
  }
}

Если вам интересно, этот тип кода часто вызывается следующим образом:

for(File f : someListOfFiles) {
   new Foo(f);
}

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

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

В любом случае, есть ли причина делать что-то подобное на любом языке программирования, или это просто очередная подача в Daily WTF?

Kane
источник
18
Похоже, это было написано разработчиками, которые знают об public static void main(string[] args)объектах и ​​слышали о них, а затем пытались их объединить.
Энди Хант
Если вам больше никогда не позвонят, вы должны сделать это там. Многие современные фреймворки, например, для внедрения зависимостей, нуждаются в создании bean-компонентов, настройке свойств и вызове THEN, и тогда вы не сможете использовать эти тяжелые рабочие.
4
Смежный вопрос: есть ли проблемы с выполнением основной работы класса в его конструкторе? , Это немного отличается, потому что созданный экземпляр используется впоследствии.
слеске
Я действительно не хочу расширять это до полного ответа, но позвольте мне сказать: есть сценарии, где это возможно приемлемо. Этот очень далек от этого. Здесь объект создается без фактического использования объекта. В отличие от этого, если вы создавали какую-то структуру данных из XML-файла, инициирование синтаксического анализа из конструктора не совсем ужасно. На самом деле в языках, которые допускают перегрузку конструкторов, я бы не стал убивать вас как сопровождающего;)
back2dos

Ответы:

60

Хорошо, идем вниз по списку:

Меня давно учили, что создание экземпляров объектов в цикле вообще плохая идея

Ни на каких языках я не пользовался.

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

В общем, если вам нужен объект внутри цикла, создайте его.

Конструкторы должны сделать минимум работы

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

есть ли причина делать что-то подобное на любом языке программирования, или это просто очередная подача в Daily WTF?

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

  • Неправильное использование классов: класс в основном действует как функция. Как вы упомянули, она должна быть либо заменена статической функцией, либо функция должна быть просто реализована в классе, который ее вызывает. Зависит от того, что он делает и где он используется.
  • Снижение производительности: в зависимости от языка создание объекта может выполняться медленнее, чем вызов функции.
  • Общая путаница: программисту обычно непонятно, как использовать этот код. Не увидев его использования, никто не узнает, как автор намеревался использовать данный код.
riwalk
источник
Спасибо за ваш ответ. Что касается «создания объектов в цикле», об этом предупреждают многие инструменты статического анализа, и я также слышал об этом от некоторых преподавателей колледжа. Конечно, это может быть пережитком 90-х годов, когда производительность выросла. Конечно, если вам это нужно, сделайте это, но я удивлен, услышав противоположную точку зрения. Спасибо за расширение моего горизонта.
Кейн
8
Создание объектов в цикле - незначительный запах кода; Вы должны посмотреть на него и спросить: «Мне действительно нужно создавать экземпляр этого объекта несколько раз?». Если вы создаете экземпляр одного и того же объекта с одними и теми же данными и говорите ему, чтобы он делал то же самое, вы сохраните циклы (и не будете тратить память), создав его один раз, а затем только повторяете инструкцию с любыми инкрементными изменениями, которые могут потребоваться для состояния объекта , Однако, если вы, например, читаете поток данных из БД или другого ввода и создаете серию объектов для хранения этих данных, непременно создайте экземпляр.
KeithS
3
Короткая версия: не используйте объектные экземпляры, как если бы они были функцией.
Эрик Реппен
27

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

Попытка хорошего примера :

Я не уверен, является ли это использование анти-паттерном, но в C # я видел код, который был примерно таким (пример несколько вымышленный):

using (ImpersonationContext administrativeContext = new ImpersonationContext(ADMIN_USER))
{
    // Perform actions here under the administrator's security context
}
// We're back to "normal" privileges here

В этом случае ImpersonationContextкласс выполняет всю свою работу в конструкторе и методе Dispose. Он использует преимущество usingоператора C # , который довольно хорошо понимается разработчиками C #, и, таким образом, гарантирует, что настройка, которая происходит в конструкторе, откатывается, как только мы находимся вне usingоператора, даже если возникает исключение. Это, вероятно, лучшее использование, которое я видел для этого (то есть «работа» в конструкторе), хотя я все еще не уверен, что считаю это «хорошим». В этом случае это довольно очевидно, функционально и лаконично.

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

Комментарии к вашему примеру:

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

Я изо всех сил пытаюсь найти цитату, но в книге Грэди Буча «Object Solutions» есть анекдот о команде разработчиков C, переходящих на C ++. По-видимому, они прошли обучение, и руководство заявило им, что они должны делать что-то «объектно-ориентированное». Чтобы выяснить, что не так, автор использовал инструмент метрики кода и обнаружил, что среднее число методов в классе равняется ровно 1, и являются вариациями фразы «делай это». Должен сказать, что раньше я никогда не сталкивался с этой конкретной проблемой, но, очевидно, это может быть признаком того, что люди вынуждены создавать объектно-ориентированный код, не понимая, как это сделать и почему.

Даниэль Б
источник
1
Ваш хороший пример - это пример Распределения ресурсов - Инициализация . Это рекомендуемый шаблон в C ++, потому что он значительно облегчает написание кода, безопасного для исключений. Я не знаю, переносится ли это на C #, хотя. (В этом случае выделяемый «ресурс» является административными привилегиями.)
zwol
@ Зак я никогда не думал об этом в таких терминах, хотя я знал о RAII в C ++. В мире C # это называется шаблоном Одноразового использования, и очень рекомендуется для всего, что имеет (как правило, неуправляемые) ресурсы, освобождать после его жизни. Основное отличие этого примера в том, что вы обычно не используете дорогие операторы в конструкторе. Например, метод SqlConnection Open () все еще должен вызываться после конструктора.
Даниэль Б
14

Нет.

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

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

Кевин А. Науде
источник
7

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

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

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

Скотт Уитлок
источник
4

Есть ли когда-нибудь причина выполнять всю работу с объектом в конструкторе?

Нет.

  • Конструктор не должен иметь побочных эффектов.
    • Все, кроме инициализации частного поля, следует рассматривать как побочный эффект.
    • Конструктор с побочными эффектами нарушает принцип единой ответственности (SRP) и противоречит духу объектно-ориентированного программирования (ООП).
  • Конструктор должен быть легким и никогда не должен выходить из строя.
    • Например, я всегда дрожу, когда вижу блок try-catch внутри конструктора. Конструктор не должен выбрасывать исключения или регистрировать ошибки.

Можно разумно поставить под сомнение эти рекомендации и сказать: «Но я не следую этим правилам, и мой код работает нормально!» На это я бы ответил: «Ну, это может быть правдой, пока это не так».

  • Исключения и ошибки внутри конструктора очень неожиданны. Если им не будет сказано, будущие программисты не будут склонны окружать эти вызовы конструктора защитным кодом.
  • Если что-то не получается в процессе, сгенерированная трассировка стека может быть трудна для анализа. Вершина трассировки стека может указывать на вызов конструктора, но в конструкторе происходит много вещей, и он может не указывать на фактический сбой LOC.
    • Я проанализировал много следов стека .NET, где это было так.
Джим Г.
источник
1
«Если им не будет сказано, будущие программисты не будут склонны окружать эти вызовы конструктора защитным кодом». - Хорошая точка зрения.
Грэм
2

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

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

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

Andrea
источник
3
Я уверен, что этот конкретный программист никогда не слышал фразу «функциональное программирование».
Кейн
Я не думаю, что программист пытался создать абстракцию замыкания. Эквивалент Java потребует абстрактного метода (планирование возможного) полиморфизма. Не доказательство этого здесь.
Кевин А. Науде
1

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

rmaruszewski
источник
1

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

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

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

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

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

jmoreno
источник
1

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

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

var parser = XMLParser()
var x = parser.parse(string)
string a = x.LookupTag(s)

действительно не лучше, чем

 var x = ParsedXML(string)
 string a = x.LookupTag(s)

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

x.UpdateView(v)

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

var x = new ComputeStatistics(inputFile)
int max = x.MaxOptions;
int mean = x.AverageOption;
int sd = x.StandardDeviation;
int k = x.Kurtosis;
...

Так что, как совместный программист на Python / C #, это не кажется мне таким уж странным.

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

На ум приходит один случай, когда конструктор / деструктор выполняет всю работу:

Замки. Обычно вы никогда не взаимодействуете с замком в течение его жизни. Использование архитектуры C # хорошо для обработки таких случаев без явной ссылки на деструктор. Однако не все блокировки реализованы таким образом.

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

Лорен Печтель
источник
-1

Я согласен с AndyBursh. Кажется, нехватка знаний вопроса.

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

fabiopagoti
источник
-4

«Меня давно учили, что создание объектов в цикле, как правило, плохая идея»

Да, возможно, вы вспоминаете, зачем нам нужен StringBuilder.

Есть две школы Java.

Создатель был продвинут первым , который учит нас повторному использованию (потому что разделение = идентичность для эффективности). Тем не менее, в начале 2000 года я начал читать, что создание объектов в Java настолько дешево (в отличие от C ++), а GC настолько эффективен, что повторное использование бесполезно, и единственное, что имеет значение, - это производительность программиста. Для повышения производительности он должен написать управляемый код, что означает модульность (то есть сужение области видимости). Так,

это плохо

byte[] array = new byte[kilos or megas]
for_loop:
  use(array)

это хорошо.

for_loop:
  byte[] array = new byte[kilos or megas]
  use(array)

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

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

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

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

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

«не делайте ничего в вашем конструкторе. Это только для инициализации»

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

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