Я перегляну ваші моменти чисельно, але по-перше, ви повинні бути дуже обережними: не плутайте, як користувач використовує бібліотеку з тим, як бібліотека реалізована . Хорошими прикладами цього є Entity Framework (який ви самі цитуєте як хорошу бібліотеку) та MVC ASP.NET. Обидва вони роблять дуже багато під капотом, наприклад, з відображенням, що абсолютно не вважатиметься хорошим дизайном, якщо ви поширюєте його через щоденний код.
Ці бібліотеки абсолютно не "прозорі" у тому, як вони працюють чи що вони роблять за кадром. Але це не шкодить, оскільки вони підтримують хороші принципи програмування у своїх споживачів . Отож, коли ви говорите про подібну бібліотеку, пам’ятайте, що як споживач бібліотеки, не турбуватися про її реалізацію чи підтримку - не ваша робота. Вам слід потурбуватися лише про те, як це допомагає чи перешкоджає написаному вами коду, який використовує бібліотека. Не плутайте ці поняття!
Отже, пройти через пункт:
Відразу ми доходимо до того, що я можу лише припустити, є прикладом вище. Ви кажете, що контейнери IOC встановлюють більшість залежностей як статичні. Ну, можливо, якась детальна інформація про те, як вони працюють, включає статичне зберігання (хоча зважаючи на те, що вони, як правило, мають примірник об'єкта на зразок Ninject IKernel
як основного сховища, я сумніваюся навіть у цьому). Але це не ваша турбота!
Насправді контейнер IoC настільки ж явний за обсягом, як і ін'єкція залежних людей. (Я буду продовжувати порівнювати контейнери IoC з ІД бідного чоловіка, тому що порівнювати їх з жодним ІД не було б несправедливо і заплутано. І, якщо бути зрозумілим, я не використовую "бідного чоловіка" як пейоратив.)
У ІД бідного чоловіка ви б вручну встановили залежність і ввели її до класу, який їй потрібен. У момент, коли ви будуєте залежність, ви вибираєте, що з нею робити - зберігайте її в локально зміненій змінній, змінній класу, статичній змінній, взагалі не зберігайте її. Ви можете передавати один і той же екземпляр у безліч класів або створити новий для кожного класу. Що б там не було. Суть полягає в тому, щоб побачити, що відбувається, ви дивитесь на точку - можливо, біля кореня програми - де створена залежність.
А що з контейнером IoC? Ну, ви робите точно так само! Знову ж , йдучи по термінології Ninject, ви подивіться на те, де зв'язування встановлено, і знайти , чи говорить це що - щось на зразок InTransientScope
, InSingletonScope
або що завгодно. Якщо що-небудь, це потенційно зрозуміліше, тому що у вас є код саме там, декларуючи його область, а не потрібно шукати метод відстеження того, що відбувається з певним об'єктом (об’єкт може бути віднесений до блоку, але чи багато разів він використовується в цьому блок або просто один раз, наприклад). Тож, можливо, вас відштовхує думка про необхідність використання функції контейнера IoC, а не сирої мови, щоб диктувати область, але поки ви довіряєте своїй бібліотеці IoC, що вам слід! .
Я досі не знаю, про що ти тут говориш. Чи контейнери IoC розглядають приватні об'єкти як частину їх внутрішньої реалізації? Я не знаю, чому вони це зробили, але знову ж таки, якщо вони будуть, це не ваше питання про те, як реалізована бібліотека, яку ви використовуєте.
Або, можливо, вони виявляють таку можливість, як впорскування в приватні сетери? Чесно кажучи, я ніколи з цим не стикався, і я сумніваюся, чи справді це спільна риса. Але навіть якщо він є, це простий випадок інструменту, яким можна неправильно користуватися. Пам'ятайте, що навіть без контейнера IoC, це лише кілька рядків коду відображення для доступу та зміни приватної власності. Це те, що ви майже ніколи не повинні робити, але це не означає, що .NET поганий для виявлення можливостей. Якщо хтось настільки очевидно і дико зловживає інструментом, це вина, а не людина.
Кінцева точка тут схожа на 2. Переважна більшість часу контейнери IoC DO використовують конструктор! Інжекція сетеру пропонується для дуже конкретних обставин, коли з певних причин конструкторське введення не може бути використане. Кожен, хто весь час використовує ін'єкцію сетера, щоб приховати, скільки залежностей передається, масово використовує цей інструмент. В цьому НЕ винна інструмент, це їхня сила.
Тепер, якби це було справді простою помилкою, що невинно зробити, і такою, яку контейнери IoC заохочують, то гаразд, можливо, ви мали б справу. Було б як зробити так, щоб кожен член мого класу був публічним, а потім звинувачував інших, коли вони змінювали речі, які не повинні, правда? Але кожен, хто використовує ін'єкцію сетера для приховування порушень SRP, або навмисно ігнорує, або зовсім не знає основних принципів дизайну. Покладати провину за це на контейнер IoC нерозумно.
Це особливо вірно, тому що це також те, що ви можете зробити так само легко з DI DI бідолахи:
var myObject
= new MyTerriblyLargeObject { DependencyA = new Thing(), DependencyB = new Widget(), Dependency C = new Repository(), ... };
Тож справді ця турбота здається абсолютно ортогональною щодо того, використовується чи не контейнер IoC.
Зміна способів спільної роботи ваших занять не є порушенням OCP. Якби це було, то вся інверсія залежності сприяла б порушенням OCP. Якби це було так, вони не були б обома однаковою абревіатурою SOLID!
Крім того, ні пункти а), ні б) не наближаються до того, що має щось спільне з OCP. Я навіть не знаю, як відповісти на це стосовно OCP.
Єдине, про що я можу здогадатися, це те, що ви думаєте, що OCP має щось спільне з поведінкою, яка не змінюється під час виконання, або з того, звідки в коді контролюється життєвий цикл залежностей. Це не. OCP не потребує модифікації існуючого коду, коли вимоги додаються або змінюються. Вся справа в написанні коду, а не в тому, як склеєний вами код (хоча, звичайно, нещільне з'єднання є важливою частиною досягнення OCP).
І ще одне останнє, що ви говорите:
Але я не можу покластися на цю саму довіру сторонній інструмент, який змінює мій скомпільований код для вирішення обхідних завдань, які я не зможу зробити вручну.
Так, можна . Для вас немає абсолютно жодної причини думати, що ці інструменти, на які покладається величезна кількість проектів, є більш глючними або схильними до несподіваної поведінки, ніж будь-яка інша стороння бібліотека.
Додаток
Я щойно помітив, що ваші вступні параграфи теж можуть використовувати деякі адреси. Ви сардонічно говорите, що контейнери IoC "не використовують таємну техніку, про яку ми ніколи не чули", щоб уникнути безладного, схильного до дублювання коду для створення графіка залежності. І ти абсолютно прав, те, що вони роблять, - це насправді вирішувати ці речі тими ж основними прийомами, що і ми завжди програмісти.
Дозвольте мені поговорити за гіпотетичним сценарієм. Ви, як програміст, зібрали велику програму, і в точці входу, де ви будуєте свій об’єктний графік, ви помітите, що у вас досить безладний код. Існує досить багато класів, які використовуються знову і знову, і кожного разу, коли ви будуєте один з тих, вам доведеться будувати під ними весь ланцюг залежностей. Крім того, ви виявите, що у вас немає ніякого виразного способу декларування або контролю життєвого циклу залежностей, за винятком спеціального коду для кожної з них. Ваш код неструктурований та повний. Це безладдя, про яке ви говорите у своєму вступному пункті.
Отже, спочатку ви почнете трохи рефакторировать - там, де якийсь повторний код структурований достатньо, ви витягуєте його в допоміжні методи тощо. Але тоді ви починаєте думати - це проблема, яку я, можливо, міг би вирішити в загальному розумінні - та, яка не є специфічною для даного конкретного проекту, але могла б допомогти вам у всіх ваших майбутніх проектах?
Отже, ви сідайте і думаєте про це і вирішите, що повинен бути клас, який може вирішити залежності. І ви замальовуєте, які публічні методи знадобляться:
void Bind(Type interfaceType, Type concreteType, bool singleton);
T Resolve<T>();
Bind
каже, "де ви бачите аргумент типу конструктора interfaceType
, передайте екземпляр concreteType
". Додатковий singleton
параметр говорить про те, чи потрібно використовувати один і той же примірник concreteType
кожного разу або завжди робити новий.
Resolve
просто спробує побудувати T
з будь-яким конструктором, який він може виявити, чиї аргументи - всі типи, які раніше були пов'язані. Він також може називати себе рекурсивно, щоб вирішити залежності до кінця. Якщо він не може вирішити екземпляр, оскільки не все було пов'язано, він видає виняток.
Ви можете спробувати реалізувати це самостійно, і вам здасться, що вам потрібно трохи роздумувати, а також кешувати прив’язки, де singleton
це правда, але, звичайно, нічого різкого чи жахливого. І як тільки ви закінчите - вуаля , у вас є ядро вашого власного контейнера IoC! Невже це так страшно? Єдина реальна різниця між цим і Ninject або StructureMap або Castle Windsor або будь-яким іншим, який ви віддаєте перевагу, полягає в тому, що вони мають набагато більше функціональних можливостей для покриття (багатьох!) Випадків використання, коли цієї базової версії було б недостатньо. Але в основі є те, що у вас є суть контейнера IoC.
IOC
іExplicitness of code
саме те, з чим я маю проблеми. Посібник DI легко може стати справжньою справою, але, принаймні, він розміщує в собі одне явне програмне забезпечення в одному місці - "Ви отримуєте те, що бачите, бачите те, що отримуєте". Хоча прив'язки та декларації контейнерів МОК легко можуть стати прихованою паралельною програмою самостійно.