Я получаю инъекцию зависимости, но может ли кто-нибудь помочь мне понять необходимость контейнера IoC?

15

Я прошу прощения, если это кажется еще одним повторением вопроса, но каждый раз, когда я нахожу статью, касающуюся этой темы, в основном это просто говорит о том, что DI. Итак, я получаю DI, но я пытаюсь понять потребность в контейнере IoC, в который, кажется, все входят. Действительно ли смысл контейнера IoC просто «автоматически разрешать» конкретную реализацию зависимостей? Может быть, мои классы, как правило, не имеют нескольких зависимостей, и, возможно, именно поэтому я не вижу ничего сложного, но я хочу убедиться, что я правильно понимаю полезность контейнера.

Обычно я разбиваю свою бизнес-логику на класс, который может выглядеть примерно так:

public class SomeBusinessOperation
{
    private readonly IDataRepository _repository;

    public SomeBusinessOperation(IDataRespository repository = null)
    {
        _repository = repository ?? new ConcreteRepository();
    }

    public SomeType Run(SomeRequestType request)
    {
        // do work...
        var results = _repository.GetThings(request);

        return results;
    }
}

Таким образом, он имеет только одну зависимость, а в некоторых случаях он может иметь вторую или третью, но не так часто. Поэтому все, что вызывает это, может передать свое собственное репо или позволить ему использовать репо по умолчанию.

Что касается моего текущего понимания контейнера IoC, то все, что он делает - это разрешает IDataRepository. Но если это все, что он делает, то я не вижу в этом тонны ценности, поскольку мои операционные классы уже определяют запасной вариант, когда никакая зависимость не передается. Поэтому единственное другое преимущество, о котором я могу думать, это то, что если у меня есть несколько таких операций, как это использует тот же резервный репо, я могу изменить это репо в одном месте, которое является реестром / фабрикой / контейнером. И это здорово, но так ли это?

Sinaesthetic
источник
1
Часто наличие резервной версии зависимости по умолчанию не имеет смысла.
Бен Ааронсон,
Что ты имеешь в виду? «Откат» - это конкретный класс, который используется почти все время, за исключением юнит-тестов. По сути, это будет тот же класс, зарегистрированный в контейнере.
Синастетический
Да, но с контейнером: (1) все другие объекты в контейнере получают один и тот же экземпляр ConcreteRepositoryи (2) вы можете предоставить дополнительные зависимости ConcreteRepository(например, подключение к базе данных будет обычным).
Жюль
@Sinaesthetic Я не имею в виду, что это всегда плохая идея, но часто она не подходит. Например, это помешает вам следовать луковой архитектуре со ссылками на ваш проект. Также может не быть четкой реализации по умолчанию. И, как говорит Жюль, контейнеры IOC управляют не только выбором типа зависимости, но и такими вещами, как совместное использование экземпляров и управление жизненным циклом
Бен Ааронсон,
Я собираюсь сделать футболку с надписью «Параметры функции - ОРИГИНАЛЬНАЯ инъекция зависимостей!»
Грэм

Ответы:

2

Контейнер IoC не относится к случаю, когда у вас есть одна зависимость. Речь идет о случае, когда у вас есть 3 зависимости, и у них есть несколько зависимостей, которые имеют зависимости и т.д.

Это также поможет вам централизовать разрешение зависимостей и управление жизненными циклами зависимостей.

Подписать
источник
10

Существует ряд причин, по которым вы можете использовать контейнер IoC.

Unreferenced dll

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

Избегайте использования new

Контейнер IoC означает, что вы можете полностью исключить использование newключевого слова для создания класса. Это имеет два эффекта. Во-первых, это разъединяет ваши классы. Второе (что связано) заключается в том, что вы можете добавить макеты для модульного тестирования. Это невероятно полезно, особенно когда вы взаимодействуете с длительным процессом.

Написать против абстракций

Использование контейнера IoC для разрешения ваших конкретных зависимостей позволяет вам писать свой код против абстракций, а не реализовывать каждый конкретный класс, который вам нужен, когда вам это нужно. Например, вам может понадобиться ваш код для чтения данных из базы данных. Вместо того, чтобы писать класс взаимодействия с базой данных, вы просто пишете интерфейс для него и код против этого. Вы можете использовать макет для проверки функциональности кода, который вы разрабатываете, по мере его разработки, вместо того, чтобы полагаться на разработку конкретного класса взаимодействия с базой данных, прежде чем вы сможете протестировать другой код.

Избегайте хрупкого кода

Другая причина использования контейнера IoC заключается в том, что, полагаясь на контейнер IoC для разрешения ваших зависимостей, вы избегаете необходимости изменять каждый отдельный вызов конструктора класса при добавлении или удалении зависимости. Контейнер IoC автоматически разрешит ваши зависимости. Это не большая проблема, когда вы создаете класс один раз, но это гигантская проблема, когда вы создаете класс в сотне мест.

Управление жизненным циклом и неуправляемая очистка ресурсов

Последняя причина, которую я упомяну, - это управление временем жизни объекта. Контейнеры IoC часто предоставляют возможность указать время жизни объекта. Имеет смысл указывать время жизни объекта в контейнере IoC, а не пытаться вручную управлять им в коде. Ручное управление временем жизни может быть очень сложным. Это может быть полезно при работе с объектами, которые требуют утилизации. Вместо того, чтобы вручную управлять удалением ваших объектов, некоторые контейнеры IoC будут управлять удалением для вас, что может помочь предотвратить утечки памяти и упростить вашу кодовую базу.

Проблема с примером кода, который вы предоставили, состоит в том, что класс, который вы пишете, имеет конкретную зависимость от класса ConcreteRepository. Контейнер IoC удалит эту зависимость.

Стивен
источник
22
Это не преимущества контейнеров IoC, а преимущества внедрения зависимостей, которые можно легко сделать с помощью DI бедного человека
Бен Ааронсон,
Написание хорошего кода DI без контейнера IoC может быть довольно сложным. Да, некоторые преимущества частично совпадают, но все эти преимущества лучше всего использовать с контейнером IoC.
Стивен
Ну, последние две причины, которые вы добавили после моего комментария, более специфичны для каждого контейнера и, на мой взгляд, являются очень вескими аргументами
Бен Ааронсон,
«Избегайте использования нового» - также препятствует статическому анализу кода, поэтому вы должны начать использовать что-то вроде этого: hmemcpy.github.io/AgentMulder . Другие преимущества, которые вы описываете в этом параграфе, связаны с DI, а не с IoC. Также ваши классы будут по-прежнему связаны, если вы будете избегать использования новых, но будете использовать конкретные типы вместо интерфейсов для параметров.
День
1
В целом IoC выглядит как нечто без изъянов, например, нет упоминания о большом недостатке: добавление класса ошибок во время выполнения вместо времени компиляции.
День
2

Согласно принципу единой ответственности каждый класс должен иметь только одну ответственность. Создание новых экземпляров классов является еще одной обязанностью, поэтому вы должны инкапсулировать этот вид кода в один или несколько классов. Вы можете сделать это с помощью любых шаблонов создания, например, фабрик, сборщиков, DI-контейнеров и т. Д.

Есть и другие принципы, такие как инверсия управления и инверсия зависимостей. В этом контексте они связаны с созданием зависимостей. Они утверждают, что классы высокого уровня должны быть отделены от классов низкого уровня (зависимостей), которые они используют. Мы можем отделить вещи, создавая интерфейсы. Таким образом, классы низкого уровня должны реализовывать определенные интерфейсы, а классы высокого уровня должны использовать экземпляры классов, которые реализуют эти интерфейсы. (примечание: единообразное ограничение интерфейса REST применяет тот же подход на системном уровне.)

Сочетание этих принципов на примере (извините за некачественный код, я использовал какой-то специальный язык вместо C #, так как я этого не знаю):

  1. Нет SRP, нет IoC

    class SomeHighLevelService
    {
        public doFooBar(){
            Crap crap = doFoo();
            doBar(crap);
        }
    
        public Crap doFoo(){
            //...
            return crap;
        }
    
        public doBar(Crap crap){
            //...
        }
    }
    
    SomeHighLevelService service = new SomeHighLevelService();
    service.doFooBar();
  2. Ближе к SRP, нет IoC

    class SomeHighLevelService
    {
        public SomeHighLevelService(){
            Foo foo = new Foo();
            Bar bar = new Bar();
        }
    
        public doFooBar(){
            Crap crap = foo.doFoo();
            bar.doBar(crap);
        }
    }
    
    class Foo {
        public Crap doFoo(){
            //...
            return crap;
        }
    }
    
    class Bar {
        public doBar(Crap crap){
            //...
        }
    }
    
    SomeHighLevelService service = new SomeHighLevelService();
    service.doFooBar();
  3. Да SRP, нет IoC

    class HighLevelServiceProvider {
        public SomeHighLevelService getSomeHighLevelService(){
            SomeHighLevelService service = new SomeHighLevelService();
            service.setFoo(this.getFoo());
            service.getBar(this.getBar());
            return service;
        }
    
        private Foo getFoo(){
            return new Foo();
        }
    
        private Bar getBar(){
            return new Bar();
        }
    }
    
    class SomeHighLevelService
    {           
        public setFoo(Foo foo){
            this.foo = foo;
        }
    
        public setBar(Bar bar){
            this.bar = bar;
        }
    
        public doFooBar(){
            Crap crap = foo.doFoo();
            bar.doBar(crap);
        }
    
    }
    
    class Foo {
        public Crap doFoo(){
            //...
            return crap;
        }
    }
    
    class Bar {
        public doBar(Crap crap){
            //...
        }
    }
    
    HighLevelServiceProvider provider = new HighLevelServiceProvider();
    SomeHighLevelService service = provider.getSomeHighLevelService();
    service.doFooBar();
  4. Да SRP, да IoC

    interface HighLevelServiceProvider {
        SomeHighLevelService getSomeHighLevelService();
    }
    
    interface SomeHighLevelService {
        doFooBar();
    }
    
    interface Foo {
        Crap doFoo();
    }
    
    interface Bar {
        doBar(Crap crap);
    }
    
    
    class ConcreteHighLevelServiceContainer implements HighLevelServiceProvider {
        public SomeHighLevelService getSomeHighLevelService(){
            SomeHighLevelService service = new ConcreteHighLevelService();
            service.setFoo(this.getFoo());
            service.getBar(this.getBar());
            return service;
        }
    
        private Foo getFoo(){
            return new ConcreteFoo();
        }
    
        private Bar getBar(){
            return new ConcreteBar();
        }
    }
    
    class ConcreteHighLevelService implements SomeHighLevelService
    {           
        public setFoo(Foo foo){
            this.foo = foo;
        }
    
        public setBar(Bar bar){
            this.bar = bar;
        }
    
        public doFooBar(){
            Crap crap = foo.doFoo();
            bar.doBar(crap);
        }
    
    }
    
    class ConcreteFoo implements Foo {
        public Crap doFoo(){
            //...
            return crap;
        }
    }
    
    class ConcreteBar implements Bar {
        public doBar(Crap crap){
            //...
        }
    }
    
    
    HighLevelServiceProvider provider = new ConcreteHighLevelServiceContainer();
    SomeHighLevelService service = provider.getSomeHighLevelService();
    service.doFooBar();

Таким образом, мы получили код, в котором вы можете заменить каждую конкретную реализацию другой, которая реализует тот же интерфейс c. Так что это хорошо, потому что участвующие классы отделены друг от друга, они знают только интерфейсы. Еще одно преимущество в том, что код создания экземпляров можно использовать повторно.

inf3rno
источник