Продай мене на контейнерах IoC, будь ласка


17

Я бачив декілька, які рекомендують використовувати контейнери IoC в коді. Мотивація проста. Візьміть наступний код введеної залежності:

class UnitUnderTest
{
    std::auto_ptr<Dependency> d_;
public:
    UnitUnderTest(
        std::auto_ptr<Dependency> d = std::auto_ptr<Dependency>(new ConcreteDependency)
    ) : d_(d)
    {
    }
};


TEST(UnitUnderTest, Example)
{
    std::auto_ptr<Dependency> dep(new MockDependency);
    UnitUnderTest uut(dep);
    //Test here
}

В:

class UnitUnderTest
{
    std::auto_ptr<Dependency> d_;
public:
    UnitUnderTest()
    {
        d_.reset(static_cast<Dependency *>(IocContainer::Get("Dependency")));
    }
};


TEST(UnitUnderTest, Example)
{
    UnitUnderTest uut;
    //Test here
}

//Config for IOC container normally
<Dependency>ConcreteDependency</Dependency>

//Config for IOC container for testing
<Dependency>MockDependency</Dependency>

(Наведене вище є гіпотетичним прикладом C ++)

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

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


1
Зразок коду виглядає як дійсно поганий приклад реалізації IoC. Мені лише знайомий C #, але, безумовно, є спосіб мати конструкторські інжекції та програмну конфігурацію також у C ++?
rmac

Відповіді:


12

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

Контейнер "спрощує інтерфейс" класу, але це робить дуже важливим чином. Контейнер - це вирішення проблеми, яку створює ін'єкція залежності, яка полягає в необхідності передачі залежностей всюди вниз через об'єктні графіки та по всіх функціональних областях. У вас є один невеликий приклад, який має одну залежність - що, якби цей об’єкт мав три залежності, а об'єкти, які залежали від нього, мали кілька об'єктів, які залежали від них тощо? Без контейнера об'єкти вгорі цих ланцюгів залежностей стають відповідальними за відстеження всіх залежностей у всій програмі.

Є також різні види контейнерів. Не всі вони строко введені, і не всі вони потребують файлів конфігурації.


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

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

4
TBH Я не маю багато досвіду з реалізацією DI, але подивіться на MEF в .NET, який може бути, але не повинен бути строко набраним, не має конфігураційного файлу, а конкретні реалізації інтерфейсів "зареєстровані" на самих заняттях. До речі, коли ви говорите: "конструктор піклується про їх виготовлення" - якщо це те, що ти робиш (він же "впорщик конструктора бідної людини"), контейнер вам не потрібен. Перевага контейнера перед цим полягає в тому, що контейнер централізує інформацію про всі ці залежності.
nlawalker

+1 за цей останній коментар - останнє речення є відповідь :)
Біллі ONeal

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

4

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

Я вважаю, що Guice - це програма Java IoC, яка нарешті отримала правильну конфігурацію.


Чи є подібні приклади для C ++?
Біллі ONeal

@Billy, я недостатньо знайомий із C ++, щоб можна було сказати.

0

Цей великий SO відповідь на Бен Scheirman детально деякі приклади коду в C #; деякими перевагами контейнерів IoC (DI) є:

  • Ваша ланцюг залежностей може стати вкладеною, і швидко стає непростим з'єднання їх вручну.
  • Дозволяє використовувати орієнтоване на аспекти програмування .
  • Деклараційні та вкладені транзакції бази даних.
  • Декларативний та вкладений блок роботи.
  • Ведення журналів.
  • Умови до / після публікації (Дизайн за контрактом).

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

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