Продайте меня на контейнерах 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

В большом приложении с большим количеством слоев и множеством движущихся частей недостатки становятся менее значительными по сравнению с преимуществами.

Контейнер «упрощает интерфейс» класса, но делает это очень важным способом. Контейнер является решением проблемы, которую создает внедрение зависимостей, а именно необходимость передавать зависимости повсеместно, через графы объектов и через функциональные области. У вас есть один небольшой пример, который имеет одну зависимость - что, если у этого объекта было три зависимости, а у объектов, которые зависели от него, было несколько объектов, которые зависели от них , и так далее? Без контейнера объекты наверху этих цепочек зависимостей в конечном итоге становятся ответственными за отслеживание всех зависимостей во всем приложении.

Также есть разные виды контейнеров. Не все из них имеют строковую типизацию, и не все из них требуют конфигурационных файлов.

nlawalker
источник
3
Но большой проект усугубляет проблемы, о которых я уже говорил . Если проект большой, файл конфигурации сам становится огромным магнитом зависимости, и становится еще проще, если одна опечатка приведет к неопределенному поведению во время выполнения. (т.е. наличие опечатки в файле конфигурации приведет к тому, что приведение приведет к неопределенному поведению, если будет возвращен неправильный тип). Что касается зависимостей зависимостей, то они здесь не имеют значения. Либо конструктор позаботится о их создании, либо тест будет максимизировать зависимость верхнего уровня.
Билли ONEAL
... Продолжение ... Когда программа не тестируется, конкретная зависимость по умолчанию создается на каждом шаге. В отношении других видов контейнеров, у вас есть примеры?
Билли Онил
4
TBH У меня нет большого опыта работы с реализациями DI, но посмотрите на MEF в .NET, который может быть, но не обязательно должен быть строго типизирован, не имеет конфигурационного файла, и конкретные реализации интерфейсов "зарегистрированы". на самих занятиях. Кстати, когда вы говорите: «конструктор позаботится о том, чтобы сделать их» - если это то, что вы делаете (то есть «инъекция в конструктор бедняка»), тогда вам не нужен контейнер. Преимущество контейнера по сравнению с этим заключается в том, что контейнер централизует информацию обо всех этих зависимостях.
Nlawalker
+1 за последний комментарий - последнее предложение есть ответ :)
Билли ONeal
1
Отличный ответ, который помог мне понять весь смысл. Дело не просто в том, чтобы упростить шаблон, а в том, чтобы элементы в верхней части цепочки зависимостей были такими же наивными, как и элементы в нижней части.
Кристофер Берман
4

Инфраструктура Guice IoC из Java основана не на файле конфигурации, а на коде конфигурации . Это означает, что конфигурация является кодом, не отличающимся от кода, составляющего ваше фактическое приложение, и может быть реорганизована и т. Д.

Я считаю, что Guice - это инфраструктура Java IoC, которая, наконец, правильно настроила конфигурацию


источник
Есть ли похожие примеры для C ++?
Билли Онил
@ Билли, я недостаточно знаком с C ++, чтобы сказать.
0

Этот великий SO ответ на Бен Scheirman подробно некоторые примеры кода в C #; некоторые преимущества IoC-контейнеров (DI):

  • Ваша цепочка зависимостей может стать вложенной, и это быстро становится громоздким, чтобы связать их вручную.
  • Позволяет использовать Аспектно-ориентированное программирование .
  • Декларативные и вложенные базы данных транзакций.
  • Декларативная и вложенная единица работы.
  • Логирование.
  • Условия до / после (дизайн по контракту).
dodgy_coder
источник
1
Я не понимаю, как ни одна из этих вещей не может быть реализована с помощью простого инжектора конструктора.
Билли ОНил
При хорошем дизайне, вам может быть достаточно инжектора конструктора или другого метода; Преимущества этих контейнеров IoC могут быть только в определенных случаях. Пример, данный с INotifyPropertyChanged в проекте WPF, был одним из таких примеров.
dodgy_coder