Введення залежності через конструктори чи властивості властивостей?


151

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

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

Відповіді:


126

Ну, це залежить :-).

Якщо клас не може виконати свою роботу без залежності, додайте його до конструктора. Класу потрібна нова залежність, тому ви хочете, щоб ваша зміна порушила речі. Також створення класу, який не повністю ініціалізований ("двоступінчаста конструкція"), є антидіаграмою (IMHO).

Якщо клас може працювати без залежності, сеттер добре.


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

3
@ Марк: Добрий момент. Однак питання полягало у додаванні залежності до існуючого класу. Тоді утримання конструктора без аргументів дозволяє підтримувати зворотну сумісність.
sleske

А як щодо того, коли залежність потрібна для функціонування, але, як правило, достатньо введення цієї залежності. Тоді чи повинна ця залежність "переоцінюватися" за властивістю чи перевантажувати конструктор?
Патрік Шалапський

@Patrick: "Клас не може виконувати свою роботу без залежності", я мав на увазі, що немає розумних замовчувань (наприклад, клас вимагає з'єднання з БД). У вашій ситуації обидва працювали б. Я все-таки зазвичай вибираю конструкторський підхід, оскільки це зменшує складність (що робити, наприклад, якщо сеттер викликається двічі)?
sleske


21

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

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

public class ClassExample
{
    public ClassExample(IDependencyOne dependencyOne, IDependencyTwo dependencyTwo)
        : this (dependnecyOne, dependencyTwo, new DependnecyThreeConcreteImpl())
    { }

    public ClassExample(IDependencyOne dependencyOne, IDependencyTwo dependencyTwo, IDependencyThree dependencyThree)
    {
        // Set the properties here.
    }
}

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


Звичайно, ланцюжок конструкторів працює лише за наявності розумного значення за замовчуванням для нового параметра. Але інакше ви не можете уникнути зламування речей ...
sleske

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

1
Я вважаю, що щодо управління ресурсами залежать такі як підключення до бази даних. Я думаю, що проблема в моєму випадку полягає в тому, що клас, до якого я додаю залежність, має кілька підкласів. У світі контейнерів IOC, де властивість буде встановлена ​​контейнером, використання сетера принаймні зніме тиск на дублювання інтерфейсу конструктора між усіма підкласами.
Niall Connaughton

17

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

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


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

11

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


11

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

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

Постарайтеся мати лише одного конструктора, це спрощує дизайн і уникає неоднозначності (якщо не для людей, то для контейнера DI).

Ви можете використовувати введення властивостей, коли у вас є те, що Марк Семанн називає локальним за замовчуванням у своїй книзі "Введення залежності в .NET": залежність не є обов'язковою, оскільки ви можете забезпечити точну робочу реалізацію, але хочете дозволити абоненту вказати інший, якщо потрібні.

(Раніше відповідь нижче)


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

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


Я б заперечував, що зазвичай зміна ін'єкції на півдорозі - це поганий стиль (тому що ви додаєте прихований стан до свого об'єкта). Але жодне правило без винятку, звичайно, ...
sleske

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

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

@Marco, це моя колишня відповідь, і ти маєш рацію. Якщо конструкторів багато, я б стверджував, що клас робить занадто багато речей :-) Або вважаю абстрактною фабрикою.
Філіп

7

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

Поки інтерфейс класів (API) зрозумілий у тому, що йому потрібно для виконання свого завдання, ви добре.


вкажіть, будь ласка, чому ви зволікаєте?
nkr1pt

3
Так, конструктори з великою кількістю аргументів погані. Ось чому ви рефакторні класи з великою кількістю параметрів конструктора :-).
sleske

10
@ nkr1pt: Більшість людей (включаючи мене) сходяться на думці, що ін'єкція сеттера погана, якщо вона дозволяє створити клас, який не працює під час виконання, якщо ін'єкція не виконується. Тому я вважаю, що хтось заперечував проти вашої заяви про те, що це особистий смак.
sleske

7

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


1
Я думаю, що офіційна назва шаблону - «Метод шаблонів».
davidjnelson

6

Я віддаю перевагу конструкторському введенню, оскільки це допомагає "нав'язати" вимоги до залежності класу. Якщо це в c'tor, споживач повинен встановити об'єкти , щоб отримати додаток для компіляції. Якщо ви використовуєте ін'єкцію сетера, вони можуть не знати, що вони мають проблеми до часу запуску - і залежно від об'єкта, це може бути пізно у часі виконання.

Я все ще час від часу використовую ін'єкцію сетера, коли ін'єктованому об'єкту, можливо, потрібна сама робота, як ініціалізація.


6

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

Я також використовую ін'єкцію властивостей для встановлення речей, на яких контейнер не має посилань на такі, як ASP.NET View на презентаторі, створеному за допомогою контейнера.

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


Дякую за вашу відповідь. Звичайно, здається, що конструктор - популярна відповідь. Однак я думаю, що це певним чином порушує інкапсуляцію. Перед введенням залежності залежно від того, що клас оголошує та інстанціює будь-які конкретні типи, необхідні для виконання своєї роботи. Завдяки DI, підкласи (і будь-які інструкції з використання вручну) тепер знають, якими інструментами використовується базовий клас. Ви додаєте нову залежність і тепер вам доведеться ланцюг примірника через усі підкласи, навіть якщо їм не потрібно використовувати залежність самостійно.
Niall Connaughton

Просто написав гарну довгу відповідь і втратив її через виняток на цьому сайті !! :( Підсумовуючи, базовий клас зазвичай використовується для повторного використання логіки. Ця логіка може досить легко перейти в підклас ... так що ви можете подумати про базовий клас і підклас = одне питання, що залежить від декількох зовнішніх об'єктів, Робіть різні роботи. Те, що у вас залежність, не означає, що вам потрібно виставляти все, що ви раніше залишали б приватним
Девід Кіфф

3

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

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

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


3

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

Використання необов'язкових параметрів може бути виконано лише в рамках, що їх підтримують, наприклад .NET 4 (як для C #, так і для VB.NET, хоча VB.NET завжди їх мав). Звичайно, ви можете домогтися подібної функціональності, просто скориставшись властивістю, яке може бути перепризначене споживачем вашого класу, але ви не отримаєте переваги незмінності, наданої, якщо об'єкт приватного інтерфейсу присвоєний параметру конструктора.

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


1

Це стара публікація, але якщо вона потрібна в майбутньому, можливо, це матиме користь:

https://github.com/omegamit6zeichen/prinject

У мене була подібна ідея і я придумав цю рамку. Це, мабуть, далеко не повне, але це ідея рамки, орієнтованої на введення властивостей


0

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


1
Навіть під час внутрішнього кодування я завжди кодую з точки зору того, що я є незалежним підрядником, який розробляє код, призначений для перерозподілу. Я припускаю, що це буде відкритим кодом. Таким чином я переконуюсь, що код є модульним і підключається та відповідає принципам SOLID.
Фред

0

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

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

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.