Чи коли-небудь є причина робити всі роботи об’єкта в конструкторі?


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?


18
Мені здається, це написали розробники, які знають про них public static void main(string[] args)і які чули про об'єкти, а потім намагалися їх збити.
Енді Хант

Якщо вас більше ніколи не викликають, ви повинні це зробити там. Багато сучасних рамок, наприклад, для ін'єкцій залежностей, потрібно створити боби, встановити властивості та ТОГО викликати, і тоді ви не можете використовувати цих важких працівників.

4
Пов'язане питання: Будь-яка проблема з виконанням основної роботи класу в його конструкторі? . Однак це дещо інше, тому що створений екземпляр використовується згодом.
sleske


Я не дуже хочу розширити це на повну відповідь, але дозвольте мені просто сказати: існують сценарії, де це, мабуть, прийнятно. Цей дуже далекий від цього. Тут об'єкт інстанціюється, фактично не потребуючи об'єкта. На відміну від цього, якщо ви створювали якусь структуру даних з XML-файлу, ініціювати аналіз з конструктора не зовсім жахливо. Насправді мовами, які дозволяють перевантажувати конструктори, я би нічого не вбив би за вас як підтримувача;)
back2dos

Відповіді:


60

Гаразд, спускаючись зі списку:

Мене давно вчили, що екземплярні об'єкти в циклі - це взагалі погана ідея

Не будь-якими мовами, якими я користувався.

В C - це гарна ідея оголосити свої змінні наперед, але це відрізняється від того, що ви сказали. Це може бути трохи швидше, якщо ви оголосите об'єкти над циклом і повторно їх використовуєте, але є безліч мов, де це збільшення швидкості буде безглуздим (і, мабуть, деякі компілятори там, які роблять оптимізацію для вас :)).

Загалом, якщо вам потрібен об’єкт в циклі, створіть його.

конструктори повинні виконати мінімум роботи

Конструктори повинні створити екземпляри полів об'єкта та зробити будь-яку іншу ініціалізацію, необхідну для того, щоб об'єкт був готовий до використання. Це, як правило, означає, що конструкторів мало, але є сценарії, коли це було б значним обсягом роботи.

Чи є колись причина робити щось подібне на будь-якій мові програмування чи це лише чергове подання до Daily WTF?

Це подання до Daily WTF. Зрозуміло, що з кодом можна зробити і гірші речі. Проблема полягає в тому, що автор має велике нерозуміння, що таке класи та як ними користуватися. Зокрема, ось що я бачу, що це неправильно з цим кодом:

  • Неправильне використання класів: Клас в основному діє як функція. Як ви вже згадували, його слід або замінити статичною функцією, або функцію просто реалізувати в класі, який її викликає. Залежить від того, що робить і де використовується.
  • Витрати на ефективність: залежно від мови створення об'єкта може бути повільніше, ніж викликати функцію.
  • Загальна плутанина: програміст зазвичай бентежить, як використовувати цей код. Не побачивши його використаним, ніхто не знав би, як автор мав намір використовувати відповідний код.

Дякую за Вашу відповідь. Що стосується "екземплярів об'єктів у циклі", то це щось, про що попереджають багато інструментів статичного аналізу, і я також чув це від деяких професорів коледжу. Зрозуміло, що це може бути затримка з 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 дійсно, хоча, мабуть, люди, як відомо, називають веб-сервіси таким чином).

Я намагаюся знайти цитату, але в книзі Грейді Буха "Об'єктні рішення" є анекдот про команду розробників C, що переходять на C ++. Мабуть, вони пройшли навчання, і керівництво їм сказало, що вони повинні робити речі, орієнтовані на об'єкти. Довідавшись, що не так, автор застосував інструмент метрики коду та виявив, що середня кількість методів на один клас становила рівно 1 та були варіантами фрази "Зроби це". Треба сказати, я ніколи раніше не стикався з цією конкретною проблемою, але, мабуть, це може бути симптомом того, що люди змушені створювати об'єктно-орієнтований код, не розуміючи, як це зробити, і чому.


1
Вашим хорошим прикладом є примірник розподілу ресурсів - ініціалізація . Це рекомендований зразок в C ++, оскільки він набагато простіше писати безпечний для винятків код. Я не знаю, чи все це переноситься на C #. (У цьому випадку "ресурс", що виділяється, є адміністративними пільгами.)
zwol

@Zack Я ніколи не думав про це в тих термінах, хоча я знав про RAII в C ++. У світі C # його називають схемою одноразового використання, і дуже рекомендується все, що має (як правило, некеровані) ресурси, звільнитись після свого життя. Основна відмінність цього прикладу полягає в тому, що ти зазвичай не робиш дорогих операторів у конструкторі. Наприклад, метод Open () SqlConnection все ще повинен викликати конструктор.
Даніель Б

14

Немає.

Якщо всі роботи виконуються в конструкторі, це означає, що об'єкт ніколи не був потрібен. Швидше за все, підрядник просто використовував об'єкт для модульності. У такому випадку неіснуючий клас зі статичним методом отримав би таку саму вигоду, не створюючи зайвих екземплярів об'єкта.

Є лише один випадок, коли виконання всіх робіт у конструкторі об'єктів є прийнятним. Саме тоді мета об'єкта полягає в тому, щоб представити довгострокову унікальну ідентичність, а не виконувати роботу. У такому випадку об'єкт зберігатиметься з інших причин, ніж виконання значущих робіт. Наприклад, одиничний екземпляр.


7

Я, безумовно, створив багато класів, які роблять багато роботи, щоб обчислити щось у конструкторі, але об'єкт є незмінним, тому він робить результати цього обчислення доступними через властивості, а потім ніколи більше нічого не робить. Це нормальна незмінність в дії.

Я думаю, що дивна річ - це вводити код з побічними ефектами в конструктор. Створення об’єкта та викидання його без доступу до властивостей чи методів виглядає дивно (але, принаймні, у цьому випадку досить очевидно, що відбувається щось інше).

Це було б краще, якби функція була перенесена на загальнодоступний метод, а потім ін'єктували екземпляр класу, а потім повторно викликати метод для циклу.


4

Чи коли-небудь є причина робити всі роботи об’єкта в конструкторі?

Немає.

  • Конструктор не повинен мати ніяких побічних ефектів.
    • Більше, ніж ініціалізація приватного поля, слід розглядати як побічний ефект.
    • Конструктор із побічними ефектами порушує принцип одиночної відповідальності (SRP) і суперечить духу об'єктно-орієнтованого програмування (OOP).
  • Конструктор повинен бути легким і ніколи не повинен виходити з ладу.
    • Наприклад, я завжди здригаюся, коли бачу блок-спроб всередині конструктора. Конструктор не повинен викидати винятки або помилки журналу.

Можна з розумом поставити під сумнів ці вказівки і сказати: "Але я не дотримуюся цих правил, і мій код працює нормально!" На це я відповів би: "Ну, це може бути правдою, поки цього не буде".

  • Винятки та помилки всередині конструктора дуже несподівані. Якщо їм не скажуть, майбутні програмісти не будуть схильні оточувати ці виклики конструктора оборонним кодом.
  • Якщо щось не вдасться у виробництві, генерований слід стека може бути важко проаналізувати. Верхня частина сліду стека може вказувати на виклик конструктора, але в конструкторі відбувається багато речей, і це може не вказувати на фактичну помилку LOC.
    • Я проаналізував багато слідів стеку .NET, де це було так.

1
"Якщо їм не кажуть, майбутні програмісти не будуть схильні оточувати ці виклики конструктора оборонним кодом." - Влучне зауваження.
Грем

2

Мені здається, що людина, яка це робила, намагалася зламати обмеження Java, яка не дозволяє передавати функції навколо як громадянам першого класу. Щоразу, коли вам потрібно передати якусь функцію, вам доведеться загортати її всередину класу методом, подібним applyабо подібним.

Тож я здогадуюсь, що він просто використав ярлик і використовував безпосередньо клас як заміну функції. Щоразу, коли ви хочете виконати функцію, ви просто інстанціюєте клас.

Це, безумовно, не дуже хороший зразок, хоча б тому, що ми тут намагаємося розібратися, але, мабуть, це найменш багатослівний спосіб зробити щось подібне до функціонального програмування на Java.


3
Я впевнений, що цей конкретний програміст ніколи не чув фразу "функціональне програмування".
Кейн

Я не думаю, що програміст намагався створити абстракцію закриття. Java-еквівалент потребує абстрактного методу (планування можливого) поліморфізму. Тут не свідчать про це.
Кевін А. Науде

1

Для мене правилом є не робити нічого в конструкторі, крім ініціалізації змінних членів. Перша причина цього полягає в тому, що це допомагає слідувати принципу SRP, оскільки зазвичай тривалий процес ініціалізації - це вказівка ​​на те, що клас робить більше, ніж повинен, і ініціалізацію слід робити десь поза класом. Друга причина полягає в тому, що таким чином передаються лише необхідні параметри, тому ви створюєте менш зв'язаний код. Конструктору зі складною ініціалізацією зазвичай потрібні параметри, які використовуються лише для побудови іншого об'єкта, але які не використовуються початковим класом.


1

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

Якщо у вас є лише один такий біт, то ваш статичний клас - це ваш конкретний клас. Отже, або у вас є конструктор, який виконує всю роботу, або одна функція, яка виконує всю роботу. Перевага робити все в конструкторі полягає в тому, що це відбувається лише один раз, він дозволяє використовувати приватні поля, властивості та методи, не переживаючи про те, що вони будуть повторно використані або введені в нитки. Якщо у вас є конструктор / метод, ви можете спокусити передати параметри єдиному методу, але якщо ви використовуєте приватні поля або властивості, що представляють потенційні проблеми з ниткою.

Навіть із класом корисності, більшість людей не думають мати вкладені статичні класи (а це може бути неможливим і залежно від мови).

В основному це відносно зрозумілий спосіб ізолювати поведінку від решти системи, в межах того, що дозволено мовою, при цьому все ж дозволяє скористатися якомога більшою мовою.

Відверто кажучи, я відповів би так само, як це зробив ваш підрядник. Це незначна корекція, щоб перетворити його на два дзвінки. Не рекомендується один інший. Які існують недоліки /, можливо, є гіпотетичнішими, ніж фактичні, чому намагаються обґрунтувати рішення, коли воно не має значення, і, ймовірно, воно не було настільки вирішено, як просто дія за замовчуванням?


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

Я згоден з ЕндіБуршем. Здається, певна проблема знань.

Однак важливо згадати рамки типу NHibernate, які вимагають просто кодувати в конструкторі (під час створення класових карт). Однак це не обмеження рамки, це лише те, що потрібно. Що стосується рефлексії, то конструктор - це єдиний метод, який завжди буде існувати (хоча це може бути приватним), і це може бути корисним, якщо вам доведеться динамічно викликати метод класу.


-4

"Мене давно вчили, що екземплярні об'єкти в циклі - це взагалі погана ідея"

Так, можливо, ви пригадуєте, для чого нам потрібен StringBuilder.

Є дві школи на Яві.

Конструктор був просунутий першим , який вчить нас повторно використовувати (тому що обмін = ідентичність до ефективності). Однак на початку 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, які тут допомагають. Вони генерують тонни лайна автоматично, як дихають.

Отже, я думаю, що вашим об'єктам не вистачає моделі "Builder" або "Factory". Недостойно створювати екземпляри безпосередньо, конструктором. Рекомендується спеціальний заводський клас для кожного вашого об'єкта.

Тепер, коли функціональне програмування починає грати, створення об'єктів стає ще простішим. Ви будете створювати предмети на кожному кроці як дихання, навіть не помічаючи цього. Тож, я думаю, ваш код стане ще «ефективнішим» у новій ері

"не робіть нічого у своєму конструкторі. Це лише для ініціалізації"

Особисто я не бачу жодної причини в керівництві. Я вважаю це просто іншим методом. Видалення коду на факторинг необхідне лише тоді, коли ви можете ним поділитися.


3
пояснення недостатньо структуровано і важко дотримуватися. Ви просто пройдете всюди зі спірним твердженням, що не мають зв'язку з контекстом.
Нова Олександрія

Справді спірне твердження: "Конструктори повинні створювати екземпляри полів об'єкта та робити будь-яку іншу ініціалізацію, необхідну для того, щоб об'єкт був готовий до використання". Скажіть це найпопулярнішим дописувачам.
Val
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.