Чи слід робити ін'єкційні залежності в ctor чи методі?


16

Поміркуйте:

public class CtorInjectionExample
{
    public CtorInjectionExample(ISomeRepository SomeRepositoryIn, IOtherRepository OtherRepositoryIn)
    {
        this._someRepository = SomeRepositoryIn;
        this._otherRepository = OtherRepositoryIn;
    }

    public void SomeMethod()
    {
        //use this._someRepository
    }

    public void OtherMethod()
    {
        //use this._otherRepository
    }
}

проти:

public class MethodInjectionExample
{
    public MethodInjectionExample()
    {
    }

    public void SomeMethod(ISomeRepository SomeRepositoryIn)
    {
        //use SomeRepositoryIn
    }

    public void OtherMethod(IOtherRepository OtherRepositoryIn)
    {
        //use OtherRepositoryIn
    }
}

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

Чи є остаточний підхід до ін’єкцій?

(NB: Я шукав інформацію про це і намагався зробити це питання об'єктивним.)


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

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

Підходить для наведених я прикладів: en.wikipedia.org/wiki/False_dilemma
StuperUser

Відповіді:


3

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


15

Правильно, спочатку давайте розберемося з "Будь-який код, що викликає ctor, потребує оновлення, коли нові залежності будуть додані"; Щоб було зрозуміло, якщо ви робите ін'єкцію залежності і у вас є код, який викликає new () на об'єкт із залежностями, ви робите це неправильно .

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

Що стосується ідеї щодо введення по методу та по класу, то щодо введення за методом існує дві основні проблеми.

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

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

var someDependency = ServiceLocator.Resolve<ISomeDependency>();
var something = classBeingInjected.DoStuff(someDependency);

Скажімо, ви збираєтесь зателефонувати за цим способом у 10 місцях вашої програми: у вас буде 10 таких фрагментів. Далі скажіть, що вам потрібно додати ще одну залежність до DoStuff (): вам доведеться змінити цей фрагмент 10 разів (або обгорнути його методом; у такому випадку ви просто реплікуєте поведінку DI вручну, що є марною часу).

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

Порівняйте це з впорскуванням у конструктор; При конструкторському введенні ви не прив'язані до конкретного контейнера DI, і ви ніколи не несете прямої відповідальності за виконання залежностей своїх класів, тому технічне обслуговування досить не болить головою.

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

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


1
"if you're doing dependency injection and you have any code calling new() on an object with dependencies, you're doing it wrong."Якщо ви випробовуєте метод методу в класі, вам доведеться його інстанціювати або ви використовуєте DI для інстанції коду під тестом?
StuperUser

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

1
Це той код, про який я говорю, коли говорю "any code calling the ctor will need updating", мій виробничий код не називає ctors. З цих причин.
StuperUser

2
@StuperUser Справедливо, але ти все ще будеш викликати методи з необхідними залежностями, а це означає, що решта моєї відповіді все ще стоїть! Якщо чесно, я погоджуюся з проблемою, що стосується введення конструктора та примушування всіх залежностей, з точки зору тестування одиниць. Я схильний використовувати властивості введення властивостей, оскільки це дозволяє вводити лише ті залежності, які, як ви очікуєте, знадобляться для тесту, і, як я вважаю, це в основному інший набір неявних викликів Assert.WasNotCalled без необхідності писати будь-який код! : D
Ед Джеймс

3

Існують різні види інверсії управління , і ваше питання пропонує лише два (ви також можете використовувати заводські для введення залежності).

Відповідь: це залежить від потреби. Іноді краще використовувати один вид, а інший інший вид.

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


3

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


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

Мені також подобається введення властивостей, просто тому ви все ще можете використовувати звичайні параметри конструктора з контейнером DI, автоматично розрізняючи розв'язану та нерозв’язану залежності. Однак, оскільки введення конструкторів та властивостей є функціонально однаковими для цього сценарію, я вирішив не згадувати про це;)
Ед Джеймс

Введення властивості змушує кожен об'єкт бути змінним і створює екземпляри в недійсному стані. Чому це було б кращим?
Кріс Пітман

2
@Chris Насправді в нормальних умовах конструктор та властивість введення властивості діють майже однаково, при цьому об'єкт повністю вводиться під час роздільної здатності. Єдиний раз, коли він може вважатися "недійсним", це під час тестування одиниць, коли ви не збираєтесь використовувати контейнер DI для інстанціювання реалізації. Дивіться мій коментар до моєї відповіді на мій погляд на те, чому це насправді вигідно. Я розумію, що можливість зміни змін може бути проблемою, але реально ви збираєтесь лише посилатися на вирішені класи через їхні інтерфейси, які не піддаватимуть залежність редагувати.
Ед Джеймс
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.