Зачем мне нужен контейнер IoC, а не простой DI-код?

Я использовал Dependency Injection (DI) некоторое время, вводя либо в конструкторе, свойстве или методе. Я никогда не чувствовал необходимости использовать контейнер Inversion of Control (IoC). Однако чем больше я читаю, тем больше давление со стороны сообщества вызывает использование контейнера IoC.

Я играл с контейнерами.NET, такими как StructureMap, NInject, Unity и Funq. Я до сих пор не понимаю, как контейнер IoC может улучшить / улучшить мой код.

Я также боюсь начать использовать контейнер на работе, потому что многие из моих коллег увидят код, который они не понимают. Многие из них могут неохотно изучать новые технологии.

Пожалуйста, убедите меня, что мне нужно использовать контейнер IoC. Я собираюсь использовать эти аргументы, когда общаюсь с коллегами-разработчиками на работе.

30 ответов

Решение

Вау, не могу поверить, что Джоэл поддержит это:

var svc = new ShippingService(new ProductLocator(), 
   new PricingService(), new InventoryService(), 
   new TrackingRepository(new ConfigProvider()), 
   new Logger(new EmailLogger(new ConfigProvider())));

через это:

var svc = IoC.Resolve<IShippingService>();

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

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


Ладно, давайте еще раз объясним это. Допустим, у вас есть некоторые сущности или объекты модели, которые вы хотите привязать к интеллектуальному интерфейсу. Этот умный пользовательский интерфейс (назовем его Shindows Morms) хочет, чтобы вы внедрили INotifyPropertyChanged, чтобы он мог отслеживать изменения и обновлять пользовательский интерфейс соответствующим образом.

"Хорошо, это звучит не так сложно", поэтому вы начинаете писать.

Вы начинаете с этого:

public class Customer
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public DateTime CustomerSince { get; set; }
    public string Status { get; set; }
}

..и в конечном итоге это:

public class UglyCustomer : INotifyPropertyChanged
{
    private string _firstName;
    public string FirstName
    {
        get { return _firstName; }
        set
        {
            string oldValue = _firstName;
            _firstName = value;
            if(oldValue != value)
                OnPropertyChanged("FirstName");
        }
    }

    private string _lastName;
    public string LastName
    {
        get { return _lastName; }
        set
        {
            string oldValue = _lastName;
            _lastName = value;
            if(oldValue != value)
                OnPropertyChanged("LastName");
        }
    }

    private DateTime _customerSince;
    public DateTime CustomerSince
    {
        get { return _customerSince; }
        set
        {
            DateTime oldValue = _customerSince;
            _customerSince = value;
            if(oldValue != value)
                OnPropertyChanged("CustomerSince");
        }
    }

    private string _status;
    public string Status
    {
        get { return _status; }
        set
        {
            string oldValue = _status;
            _status = value;
            if(oldValue != value)
                OnPropertyChanged("Status");
        }
    }

    protected virtual void OnPropertyChanged(string property)
    {
        var propertyChanged = PropertyChanged;

        if(propertyChanged != null)
            propertyChanged(this, new PropertyChangedEventArgs(property));
    }

    public event PropertyChangedEventHandler PropertyChanged;
}

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

Когда-нибудь слышали этот термин, работать умнее, а не усерднее?

Ну, представьте, какой-нибудь умный парень из вашей команды подошел и сказал: "Вот более легкий путь"

Если вы сделаете свои свойства виртуальными (успокойтесь, это не так уж сложно), мы можем автоматически влиять на поведение этих свойств. (Это называется AOP, но не беспокойтесь об имени, сосредоточьтесь на том, что он собирается сделать для вас)

В зависимости от того, какой инструмент IoC вы используете, вы можете сделать что-то похожее на это:

var bindingFriendlyInstance = IoC.Resolve<Customer>(new NotifyPropertyChangedWrapper());

Пуф! Все это руководство INotifyPropertyChanged BS теперь автоматически генерируется для вас на каждом установщике виртуальных свойств рассматриваемого объекта.

Это волшебство? ДА! Если вы можете доверять тому факту, что этот код выполняет свою работу, тогда вы можете спокойно пропустить все это свойство, оборачивая mumbo-jumbo. Вам нужно решить бизнес-проблемы.

Некоторые другие интересные применения инструмента IoC для выполнения AOP:

  • Декларативные и вложенные транзакции базы данных
  • Декларативная и вложенная единица работы
  • логирование
  • Условия до / после (дизайн по контракту)

Я с тобой, Вадим. Контейнеры IoC используют простую, элегантную и полезную концепцию и делают ее тем, что вам придется изучать в течение двух дней с помощью руководства на 200 страниц.

Я лично озадачен тем, как сообщество IoC взяло красивую, элегантную статью Мартина Фаулера и превратило ее в набор сложных фреймворков, обычно с 200-300 страницами руководств.

Я стараюсь не быть осуждающим (ХАХА!), Но я думаю, что люди, которые используют контейнеры IoC, (А) очень умны и (Б) испытывают недостаток в сочувствии к людям, которые не так умны, как они. Все имеет для них смысл, поэтому им трудно понять, что многие обычные программисты смущают эти концепции. Это проклятие знаний. Люди, которые понимают контейнеры IoC, испытывают затруднения, полагая, что есть люди, которые этого не понимают.

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

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

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

Если ваша система достигает уровня сложности, когда ручной DI становится рутинной работой (то есть увеличивает обслуживание), сравните это с кривой обучения команды каркаса контейнера DI.

Если вам нужен больший контроль над управлением временем жизни зависимостей (то есть, если вы чувствуете необходимость реализации шаблона Singleton), посмотрите на контейнеры DI.

Если вы используете DI-контейнер, используйте только те функции, которые вам нужны. Пропустите файл конфигурации XML и настройте его в коде, если этого достаточно. Придерживайтесь конструктора инъекций. Основы Unity или StructureMap можно сжать до нескольких страниц.

Есть замечательный пост в блоге Марка Симанна на эту тему: Когда использовать DI-контейнер

На мой взгляд, преимуществом IoC является возможность централизовать настройку ваших зависимостей.

Если вы используете инъекцию зависимости, ваш код может выглядеть следующим образом

public class CustomerPresenter
{
  public CustomerPresenter() : this(new CustomerView(), new CustomerService())
  {}

  public CustomerPresenter(ICustomerView view, ICustomerService service)
  {
    // init view/service fields
  }
  // readonly view/service fields
}

Если бы вы использовали статический класс IoC, в отличие от более сложных файлов конфигурации, IMHO, вы могли бы получить что-то вроде этого:

public class CustomerPresenter
{
  public CustomerPresenter() : this(IoC.Resolve<ICustomerView>(), IoC.Resolve<ICustomerService>())
  {}

  public CustomerPresenter(ICustomerView view, ICustomerService service)
  {
    // init view/service fields
  }
  // readonly view/service fields
}

Тогда ваш класс Static IoC будет выглядеть так, я использую Unity здесь.

public static IoC
{
   private static readonly IUnityContainer _container;
   static IoC()
   {
     InitializeIoC();
   }

   static void InitializeIoC()
   {
      _container = new UnityContainer();
      _container.RegisterType<ICustomerView, CustomerView>();
      _container.RegisterType<ICustomerService, CustomerService>();
      // all other RegisterTypes and RegisterInstances can go here in one file.
      // one place to change dependencies is good.
   }
}

Контейнеры IoC также хороши для загрузки глубоко вложенных зависимостей классов. Например, если у вас был следующий код, использующий Depedency Injection.

public void GetPresenter()
{
    var presenter = new CustomerPresenter(new CustomerService(new CustomerRepository(new DB())));
}

class CustomerPresenter
{
    private readonly ICustomerService service;
    public CustomerPresenter(ICustomerService service)
    {
        this.service = service;
    }
}

class CustomerService
{
    private readonly IRespository<Customer> repository;
    public CustomerService(IRespository<Customer> repository)
    {
        this.repository = repository;
    }
}

class CustomerRepository : IRespository<Customer>
{
    private readonly DB db;
    public CustomerRepository(DB db)
    {
        this.db = db;
    }
}

class DB { }

Если у вас были загружены все эти зависимости и контейнер IoC, вы можете разрешить CustomerService, и все дочерние зависимости будут автоматически разрешены.

Например:

public static IoC
{
   private IUnityContainer _container;
   static IoC()
   {
       InitializeIoC();
   }

   static void InitializeIoC()
   {
      _container = new UnityContainer();
      _container.RegisterType<ICustomerService, CustomerService>();
      _container.RegisterType<IRepository<Customer>, CustomerRepository>();
   }

   static T Resolve<T>()
   {
      return _container.Resolve<T>();
   }
}

public void GetPresenter()
{
   var presenter = IoC.Resolve<CustomerPresenter>();
   // presenter is loaded and all of its nested child dependencies 
   // are automatically injected
   // -
   // Also, note that only the Interfaces need to be registered
   // the concrete types like DB and CustomerPresenter will automatically 
   // resolve.
}

Я фанат декларативного программирования (посмотрите, на сколько вопросов SQL я отвечаю), но контейнеры IoC, на которые я смотрел, кажутся слишком загадочными для их же блага.

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

... или оба они верны в той или иной степени.

Я не думаю, что концепция контейнера IoC плохая. Но реализация должна быть достаточно мощной (то есть гибкой), чтобы быть полезной в самых разных приложениях, но при этом простой и понятной.

Это, наверное, шесть из полутора десятков других. Реальное приложение (не игрушка или демо) обязательно должно быть сложным, составляя множество угловых случаев и исключений из правил. Либо вы заключаете эту сложность в императивный код, либо в декларативный код. Но вы должны представить это где-то.

Использование контейнера в основном сводится к переходу от императивного / скриптового стиля инициализации и конфигурации к декларативному. Это может иметь несколько разных полезных эффектов:

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

Конечно, могут быть трудности:

  • Код, который требует сложного запуска / выключения / управления жизненным циклом, может быть нелегко адаптировать к контейнеру.
  • Вам, вероятно, придется разбираться с любыми личными проблемами, проблемами процесса и командной культуры, но вот почему вы спросили...
  • Некоторые из наборов инструментов сами по себе быстро набирают вес, поощряя глубокую зависимость, с которой многие DI-контейнеры начинали как обратная реакция.

Мне кажется, что вы уже создали свой собственный контейнер IoC (используя различные шаблоны, которые были описаны Мартином Фаулером) и спрашиваете, почему чья-то реализация лучше вашей.

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

Плюсы за рассмотрение стороннего контейнера IoC

  • Вы исправляете ошибки бесплатно
  • Дизайн библиотеки может быть лучше вашего
  • Люди могут быть уже знакомы с конкретной библиотекой
  • Библиотека может быть быстрее вашей
  • Он может иметь некоторые функции, которые вы хотели бы реализовать, но у вас никогда не было времени (есть ли у вас сервисный локатор?)

Cons

  • Вы получаете ошибки, бесплатно:)
  • Дизайн библиотеки может быть хуже вашего
  • Вы должны изучить новый API
  • Слишком много функций, которые вы никогда не будете использовать
  • Обычно сложнее отлаживать код, который вы не написали
  • Миграция из предыдущего контейнера IoC может быть утомительной

Итак, взвесьте свои плюсы против ваших минусов и примите решение.

Я думаю, что большая часть IoC достигается за счет использования DI. Поскольку вы уже делаете это, остальная часть выгоды является дополнительной.

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

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

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

  • Если вы хотите использовать AOP для решения нескольких сквозных задач, IoC предоставляет ловушки для перехвата вызовов методов. Это не так часто делается для проектов, но IoC делает это проще.

Ранее я писал такую ​​функциональность, но если мне понадобится какая-либо из этих функций, я бы предпочел использовать предварительно созданный и протестированный инструмент, если он соответствует моей архитектуре.

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

Я использую контейнер IoC и ценю функциональность, но должен признать, что заметил компромисс: мой код становится более понятным на уровне класса и менее четким на уровне приложения (т. Е. Визуализирует поток управления).

Я выздоравливающий наркоман МОК. Мне трудно оправдать использование МОК для DI в большинстве случаев в эти дни. Контейнеры IOC жертвуют проверкой времени компиляции и, предположительно, в ответ дают вам "простую" настройку, сложное управление временем жизни и на лету обнаружение зависимостей во время выполнения. Я считаю, что потеря проверки времени компиляции и возникающая во время выполнения магия / исключения не стоят наворотов в подавляющем большинстве случаев. В крупных корпоративных приложениях им очень трудно следить за происходящим.

Я не покупаю аргумент централизации, потому что вы также можете очень легко централизовать статическую настройку, используя абстрактную фабрику для своего приложения и неукоснительно откладывая создание объекта до абстрактной фабрики, т.е. выполняйте надлежащий DI.

Почему бы не сделать статический безмагнитный DI вроде этого:

interface IServiceA { }
interface IServiceB { }
class ServiceA : IServiceA { }
class ServiceB : IServiceB { }

class StubServiceA : IServiceA { }
class StubServiceB : IServiceB { }

interface IRoot { IMiddle Middle { get; set; } }
interface IMiddle { ILeaf Leaf { get; set; } }
interface ILeaf { }

class Root : IRoot
{
    public IMiddle Middle { get; set; }

    public Root(IMiddle middle)
    {
        Middle = middle;
    }

}

class Middle : IMiddle
{
    public ILeaf Leaf { get; set; }

    public Middle(ILeaf leaf)
    {
        Leaf = leaf;
    }
}

class Leaf : ILeaf
{
    IServiceA ServiceA { get; set; }
    IServiceB ServiceB { get; set; }

    public Leaf(IServiceA serviceA, IServiceB serviceB)
    {
        ServiceA = serviceA;
        ServiceB = serviceB;
    }
}


interface IApplicationFactory
{
    IRoot CreateRoot();
}

abstract class ApplicationAbstractFactory : IApplicationFactory
{
    protected abstract IServiceA ServiceA { get; }
    protected abstract IServiceB ServiceB { get; }

    protected IMiddle CreateMiddle()
    {
        return new Middle(CreateLeaf());
    }

    protected ILeaf CreateLeaf()
    {
        return new Leaf(ServiceA,ServiceB);
    }


    public IRoot CreateRoot()
    {
        return new Root(CreateMiddle());
    }
}

class ProductionApplication : ApplicationAbstractFactory
{
    protected override IServiceA ServiceA
    {
        get { return new ServiceA(); }
    }

    protected override IServiceB ServiceB
    {
        get { return new ServiceB(); }
    }
}

class FunctionalTestsApplication : ApplicationAbstractFactory
{
    protected override IServiceA ServiceA
    {
        get { return new StubServiceA(); }
    }

    protected override IServiceB ServiceB
    {
        get { return new StubServiceB(); }
    }
}


namespace ConsoleApplication5
{
    class Program
    {
        static void Main(string[] args)
        {
            var factory = new ProductionApplication();
            var root = factory.CreateRoot();

        }
    }

    //[TestFixture]
    class FunctionalTests
    {
        //[Test]
        public void Test()
        {
            var factory = new FunctionalTestsApplication();
            var root = factory.CreateRoot();
        }
    }
}

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

Преимущества:

  • Все настройки и настройки объекта централизованы.
  • Конфигурация это просто код
  • Проверка времени компиляции упрощает поддержку, так как вы не можете забыть обновить свои регистрации.
  • Нет волшебства отражения во время выполнения

Я рекомендую скептикам начать следующий проект по созданию "зеленого поля" и честно спросить себя, в какой момент вам нужен контейнер. Позже легко добавить контейнер IOC, поскольку вы просто заменяете фабричную реализацию модулем конфигурации контейнера IOC.

Самым большим преимуществом использования IoC-контейнеров для меня (лично я использую Ninject) было устранение передачи настроек и других видов глобальных объектов состояния.

Я не программирую для Интернета, у меня консольное приложение, и во многих местах в глубине дерева объектов мне нужен доступ к настройкам или метаданным, указанным пользователем, которые создаются в совершенно отдельной ветви дерева объектов. С IoC я просто говорю Ninject, что нужно обращаться с настройками как синглтоном (потому что всегда есть только один их экземпляр), запрашивать настройки или словарь в конструкторе и нажимать... они волшебным образом появляются, когда они мне нужны!

Без использования контейнера IoC мне пришлось бы передавать настройки и / или метаданные через 2, 3, ..., n объектов, прежде чем они фактически использовались объектом, который нуждался в этом.

У контейнеров DI / IoC есть много других преимуществ, о которых здесь подробно рассказывают другие, и переход от идеи создания объектов к запросу объектов может быть утомительным, но использование DI было очень полезно для меня и моей команды, поэтому, возможно, вы сможете добавить его в ваш арсенал!

Фреймворки IoC отлично подходят, если вы хотите...

  • ... выбросить тип безопасности. Многие (все?) Платформы IoC вынуждают вас выполнять код, если вы хотите быть уверены, что все правильно подключено. "Эй! Надеюсь, я все настроил, чтобы мои 100 инициализаций не потерпели неудачу в работе, исключая исключения с нулевым указателем!"

  • ... засорять ваш код глобальными переменными (платформы IoC предназначены для изменения глобальных состояний).

  • ... написать дрянной код с неясными зависимостями, которые трудно реорганизовать, так как вы никогда не узнаете, что зависит от чего.

Проблема с IoC заключается в том, что люди, которые их используют, писали такой код

public class Foo {
    public Bar Apa {get;set;}
    Foo() {
        Apa = new Bar();
    }
}

что явно ошибочно, поскольку зависимость между Foo и Bar жесткая. Тогда они поняли, что было бы лучше написать код, как

public class Foo {
    public IBar Apa {get;set;}
    Foo() {
        Apa = IoC<IBar>();
    }
}

что также ошибочно, но не так очевидно. В Хаскеле тип Foo() было бы IO Foo но ты действительно не хочешь IO -часть и это должно быть предупреждающим знаком, что что-то не так с вашим дизайном, если вы его получили

Чтобы избавиться от этого (часть IO), получите все преимущества IoC-фреймворков и ни одного из его недостатков, которые вы могли бы вместо этого использовать абстрактную фабрику.

Правильное решение будет что-то вроде

data Foo = Foo { apa :: Bar }

или, может быть

data Foo = forall b. (IBar b) => Foo { apa :: b }

и вводить (но я бы не назвал это вводить) бар.

Также: посмотрите это видео с Эриком Мейером (изобретателем LINQ), где он говорит, что DI предназначен для людей, которые не знают математику (и я не мог согласиться с этим): http://www.youtube.com/watch?v=8Mttjyf-8P4

В отличие от г-на Спольского, я не верю, что люди, которые используют IoC-фреймворки, очень умны - я просто считаю, что они не знают математику.

Я обнаружил, что правильная реализация Dependency Injection вынуждает программистов использовать множество других практик программирования, которые помогают улучшить тестируемость, гибкость, удобство сопровождения и масштабируемость кода: такие практики, как принцип единой ответственности, разделение проблем и кодирование против API. Такое ощущение, что я вынужден писать более модульные классы и методы размером с кусочек, что облегчает чтение кода, потому что его можно брать кусками размером с кусочек.

Но он также имеет тенденцию создавать довольно большие деревья зависимостей, которыми гораздо легче управлять через каркас (особенно если вы используете соглашения), чем вручную. Сегодня я хотел что-то очень быстро протестировать в LINQPad, и я подумал, что будет слишком сложно создать ядро ​​и загрузить свои модули, и в итоге я написал это вручную:

var merger = new SimpleWorkflowInstanceMerger(
    new BitFactoryLog(typeof(SimpleWorkflowInstanceMerger).FullName), 
    new WorkflowAnswerRowUtil(
        new WorkflowFieldAnswerEntMapper(),
        new ActivityFormFieldDisplayInfoEntMapper(),
        new FieldEntMapper()),
    new AnswerRowMergeInfoRepository());

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

Потратив некоторое время на изучение ответов и комментариев по этому вопросу, я убежден, что люди, которые выступают против использования контейнера IoC, не практикуют истинное внедрение зависимостей. Примеры, которые я видел, - это практики, которые обычно путают с внедрением зависимостей. Некоторые люди жалуются на трудности с "чтением" кода. Если все сделано правильно, подавляющее большинство вашего кода должно быть идентичным при использовании DI вручную, как при использовании контейнера IoC. Разница должна полностью заключаться в нескольких "точках запуска" в приложении.

Другими словами, если вам не нравятся контейнеры IoC, вы, вероятно, делаете инъекцию зависимостей не так, как предполагалось.

Еще один момент: внедрение зависимости действительно невозможно сделать вручную, если вы используете отражение где-либо. Хотя я ненавижу то, что отражается на навигации по коду, вы должны признать, что есть определенные области, где этого действительно не избежать. Например, ASP.NET MVC пытается создать экземпляр контроллера путем отражения в каждом запросе. Чтобы выполнить внедрение зависимостей вручную, вы должны сделать каждый контроллер "корневым контекстом", вот так:

public class MyController : Controller
{
    private readonly ISimpleWorkflowInstanceMerger _simpleMerger;
    public MyController()
    {
        _simpleMerger = new SimpleWorkflowInstanceMerger(
            new BitFactoryLog(typeof(SimpleWorkflowInstanceMerger).FullName), 
            new WorkflowAnswerRowUtil(
                new WorkflowFieldAnswerEntMapper(),
                new ActivityFormFieldDisplayInfoEntMapper(),
                new FieldEntMapper()),
            new AnswerRowMergeInfoRepository())
    }
    ...
}

Теперь сравните это с тем, что DI Framework сделает это за вас:

public MyController : Controller
{
    private readonly ISimpleWorkflowInstanceMerger _simpleMerger;
    public MyController(ISimpleWorkflowInstanceMerger simpleMerger)
    {
        _simpleMerger = simpleMerger;
    }
    ...
}

Используя структуру DI, обратите внимание, что:

  • Я могу протестировать этот класс. Создавая макет ISimpleWorkflowInstanceMergerЯ могу проверить, что он используется так, как я ожидаю, без необходимости подключения к базе данных или чего-либо еще.
  • Я использую намного меньше кода, и код намного легче читать.
  • Если одна из зависимостей моей зависимости меняется, мне не нужно вносить какие-либо изменения в контроллер. Это особенно приятно, если учесть, что несколько контроллеров могут использовать одни и те же зависимости.
  • Я никогда не ссылаюсь на классы из уровня данных. Мое веб-приложение может просто включать ссылку на проект, содержащий ISimpleWorkflowInstanceMerger интерфейс. Это позволяет мне разбивать приложение на отдельные модули и поддерживать настоящую многоуровневую архитектуру, что, в свою очередь, делает вещи более гибкими.

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

Всякий раз, когда вы используете ключевое слово "new", вы создаете конкретную зависимость от класса, и в вашей голове должен прозвучать небольшой сигнал тревоги. Становится все труднее тестировать этот объект изолированно. Решение состоит в том, чтобы запрограммировать интерфейсы и внедрить зависимость, чтобы объект мог быть протестирован модулем с чем угодно, что реализует этот интерфейс (например, mocks).

Беда в том, что вы должны где-то строить объекты. Фабричный шаблон - это один из способов снять связь с вашими POXO (Plain Old, "вставьте сюда свой язык OO", "Объекты"). Если вы и ваши коллеги пишете такой код, то контейнер IoC - это следующее "поэтапное улучшение", которое вы можете внести в свою кодовую базу. Это сместит весь этот мерзкий шаблонный код фабрики с ваших чистых объектов и бизнес-логики. Они получат это и полюбят это. Черт возьми, поговори с компанией о том, почему ты любишь это, и приведи всех в восторг.

Если ваши коллеги еще не занимаются DI, я бы посоветовал вам сосредоточиться на этом в первую очередь. Распространите информацию о том, как написать чистый код, который легко тестируется. Чистый DI-код - это сложная часть, когда вы окажетесь там, перенос логики связывания объектов из классов Factory в контейнер IoC должен быть относительно тривиальным.

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

Вам не нужен контейнер IoC.

Но если вы строго следуете шаблону DI, вы обнаружите, что его использование удалит тонну избыточного, скучного кода.

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

Просто так получилось, что я выискивал самодельный код DI и заменил его на IOC. Я, вероятно, удалил более 200 строк кода и заменил их примерно на 10. Да, мне пришлось немного изучить, как использовать контейнер (Winsor), но я инженер, работающий над интернет-технологиями в 21-го века, так что я привык к этому. Я, вероятно, потратил около 20 минут на просмотр как. Это стоило моего времени.

По мере того, как вы продолжаете разъединять свои классы и инвертировать ваши зависимости, классы продолжают оставаться маленькими, а "граф зависимостей" продолжает увеличиваться в размере. (Это неплохо.) Использование основных функций контейнера IoC делает подключение всех этих объектов тривиальным, но выполнение этого вручную может стать очень обременительным. Например, что если я хочу создать новый экземпляр "Foo", но ему нужен "Bar". А "Бар" нуждается в "А", "В" и "С". И каждому из них нужны еще 3 вещи и т. Д. И т. Д. (Да, я не могу придумать хорошие поддельные имена:)).

Использование контейнера IoC для построения вашего графа объектов снижает сложность и переводит его в одноразовую конфигурацию. Я просто говорю "создай мне" Foo "", и он выясняет, что нужно для его создания.

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

То же самое о Единстве. Становитесь слишком большими, и вы можете услышать скрип в стропилах.

Меня никогда не удивляет, когда люди начинают говорить о том, что чистый код IoC - это те же самые люди, которые когда-то говорили о том, что шаблоны в C++ были элегантным способом вернуться в 90-е годы, но в наши дни они будут воспринимать их как загадочные., Бах!

Итак, прошло уже почти 3 года, а?

  1. 50% из тех, кто высказался в пользу фреймворков IoC, не понимают разницы между фреймворками IoC и IoC. Я сомневаюсь, что они знают, что вы можете писать код без развертывания на сервере приложений

  2. Если мы возьмем самый популярный фреймворк Java Spring, то конфигурация IoC перенесена из XMl в код и теперь выглядит следующим образом

`@Configuration открытый класс AppConfig {

public @Bean TransferService transferService() {
    return new TransferServiceImpl(accountRepository());
}

public @Bean AccountRepository accountRepository() {
    return new InMemoryAccountRepository();
}

} `И нам нужна структура, чтобы сделать это, почему именно?

В мире.NET AOP не слишком популярен, поэтому для DI фреймворк - ваш единственный реальный вариант, независимо от того, пишете ли вы один или используете другой фреймворк.

Если вы использовали AOP, вы можете внедрить его при компиляции приложения, что более распространено в Java.

У DI есть много преимуществ, таких как уменьшенная связь, что упрощает модульное тестирование, но как вы будете его реализовывать? Вы хотите использовать отражение, чтобы сделать это самостоятельно?

Вот почему. Проект называется IOC-with-Ninject. Вы можете скачать и запустить его с Visual Studio. В этом примере используется Ninject, но ВСЕ операторы 'new' находятся в одном месте, и вы можете полностью изменить работу своего приложения, изменив используемый модуль связывания. Пример настроен так, что вы можете привязать к поддельной версии служб или реальной версии. В небольших проектах это может не иметь значения, но в больших проектах это большое дело.

Просто чтобы быть ясно, преимущества, как я их вижу: 1) ВСЕ новые операторы в одном месте в корне кода. 2) Полностью перефакторинг кода с одним изменением. 3) Дополнительные очки за "крутой фактор", потому что это... хорошо: круто.:п

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

Если вы используете его просто для упрощения создания объекта, я должен спросить: вы создаете экземпляр этого объекта в нескольких местах? Разве синглтон не подойдет вашим потребностям? Вы меняете конфигурацию во время выполнения? (Переключение типов источников данных и т. Д.).

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

Кто сказал, что интерфейс лучше наследования? Скажем, вы тестируете Сервис. Почему бы не использовать конструктор DI и создавать макеты зависимостей, используя наследование? Большинство услуг, которыми я пользуюсь, имеют только несколько зависимостей. Выполнение модульного тестирования таким способом предотвращает поддержку множества бесполезных интерфейсов и означает, что вам не нужно использовать Resharper, чтобы быстро найти объявление метода.

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

Во-первых, настройка контейнера в первую очередь. Затем вам все еще нужно определить каждый объект, который нужно инициализировать. Таким образом, вы не сохраняете код при инициализации, вы перемещаете его (если ваш объект не используется более одного раза. Он лучше как Singleton?). Затем для каждого объекта, который вы инициализировали таким образом, вы должны создать и поддерживать интерфейс.

У кого-нибудь есть мысли по этому поводу?

Я постараюсь выяснить, почему МОК может не подходить с моей точки зрения.

Как и во всем остальном, контейнер IOC (или, как сказал бы Эйнштейн, I=OC^2) - это концепция, которую вы должны решить для себя, нужно ли вам это в коде или нет. Последние крики моды о МОК - это только мода. Не поддавайтесь моде, это первое. Существует множество концепций, которые вы можете реализовать в своем коде. Прежде всего, я использую внедрение зависимостей, так как я начал программировать, и узнал сам термин, когда он был популяризирован под этим именем. Контроль зависимостей - очень старая тема, и до сих пор она решалась триллионами способов, в зависимости от того, что отделено от чего. Отделение всего от всего - ерунда. Проблема с контейнером IOC заключается в том, что он пытается быть таким же полезным, как Entity Framework или NHibernate. Хотя написание объектно-реляционного преобразователя просто необходимо, как только вам нужно соединить любую базу данных с вашей системой, контейнер IOC не всегда необходим. Итак, когда контейнер IOC полезен:

  1. Когда у вас есть ситуация со многими зависимостями, вы хотите организовать
  2. Когда вам не нужно связывать свой код со сторонним продуктом
  3. Когда ваши разработчики хотят научиться работать с новым инструментом

1: Нередко в вашем коде так много зависимостей, или вы знаете о них на ранних стадиях разработки. Абстрактное мышление полезно, когда необходимо абстрактное мышление.

2: Сопряжение вашего кода со сторонним кодом - проблема HuGe. Я работал с кодом, которому более 10 лет, и который в то время следовал причудливым и продвинутым концепциям ATL, COM, COM+ и так далее. Теперь вы ничего не можете сделать с этим кодом. То, что я говорю, - то, что продвинутая концепция дает очевидное преимущество, все же это отменено в долгосрочной перспективе с самим устаревшим преимуществом. Это только сделало все это более дорогим.

3: разработка программного обеспечения достаточно трудна. Вы можете расширить его до неузнаваемых уровней, если позволите какой-то продвинутой концепции врезаться в ваш код. Есть проблема с IOC2. Хотя это разъединяет зависимости, оно также разъединяет логический поток. Представьте, что вы нашли ошибку, и вам нужно установить перерыв, чтобы изучить ситуацию. IOC2, как и любая другая продвинутая концепция, делает это более сложным. Исправление ошибки в концепции труднее, чем исправление ошибки в простом коде, потому что, когда вы исправляете ошибку, концепция должна снова соблюдаться. (Просто для примера: C++ .NET постоянно меняет синтаксис настолько, что вам нужно тщательно продумать, прежде чем проводить рефакторинг какой-либо более старой версии.NET.) Так в чем же проблема с IOC? Проблема в разрешении зависимостей. Логика решения обычно скрыта в самом IOC2, и, возможно, она написана необычным образом, который вам необходимо изучить и поддерживать. Будет ли ваш сторонний продукт там через 5 лет? Microsoft не было.

Синдром "мы знаем как" написан повсеместно в отношении IOC2. Это похоже на автоматизацию тестирования. На первый взгляд удачный срок и идеальное решение, вы просто выполняете все свои тесты за ночь и видите результаты утром. Действительно больно объяснять компании за компанией, что на самом деле означает автоматизированное тестирование. Автоматическое тестирование определенно не является быстрым способом уменьшения количества ошибок, которые вы можете внести в одночасье для повышения качества вашего продукта. Но мода делает это понятие раздражающе доминирующим. IOC2 страдает тем же синдромом. Считается, что вам нужно реализовать это, чтобы ваше программное обеспечение было хорошим. Каждое недавнее интервью меня спросили, внедряю ли я IOC2 и автоматизацию. Это признак моды: у компании есть часть кода, написанная на MFC, которую они не откажутся.

Вы должны изучить IOC2, как и любое другое понятие в программном обеспечении. Решение о необходимости использования IOC2 принимается командой и компанией. Однако, по крайней мере, ВСЕ вышеупомянутые аргументы должны быть упомянуты до принятия решения. Только если вы видите, что положительная сторона перевешивает отрицательную сторону, вы можете принять положительное решение.

В IOC2 нет ничего плохого, кроме того, что он решает только те проблемы, которые он решает, и вводит проблемы, которые он вводит. Ничего больше. Однако идти против моды очень сложно, у них вспотел рот, приверженцы чего-либо. Странно, что никого из них нет, когда проблема с их причудой становится очевидной. Многие концепции в индустрии программного обеспечения были защищены, потому что они приносят прибыль, пишутся книги, проводятся конференции, производятся новые продукты. Это мода, обычно недолговечная. Как только люди находят что-то еще, они полностью от них отказываются. IOC2 полезен, но показывает те же признаки, что и многие другие исчезнувшие концепции, которые я видел. Я не знаю, выживет ли это. Для этого нет правила. Вы думаете, если это будет полезно, оно выживет. Нет, это не так. Достаточно одной большой богатой компании, и концепция может умереть в течение нескольких недель. Посмотрим. NHibernate выжил, EF занял второе место. Может быть, IOC2 тоже выживет. Не забывайте, что большинство концепций в разработке программного обеспечения не представляют собой ничего особенного, они очень логичны, просты и очевидны, и иногда сложнее запомнить текущее соглашение об именах, чем понять саму концепцию. Знание IOC2 делает разработчика лучшим разработчиком? Нет, потому что, если разработчик не смог придумать концепцию, схожую по своей природе с IOC2, ему или ей будет трудно понять, какую проблему решает IOC2, используя ее, будет выглядеть искусственно, и он или она может начать ее использовать. ради того, чтобы быть своего рода политкорректным.

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

Вам не нужна структура для достижения внедрения зависимости. Вы можете сделать это с помощью основных Java-концепций. http://en.wikipedia.org/wiki/Dependency_injection

Я понимаю, что это довольно старый пост, но он все еще достаточно активен, и я подумал, что хотел бы добавить пару моментов, которые еще не были упомянуты в других ответах.

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

1) Наличие сторонней библиотеки, управляющей временем жизни ваших объектов "автоматически", может привести к неожиданным результатам. Мы обнаружили, что особенно в больших проектах у вас может быть гораздо больше копий объекта, чем вы ожидаете, и больше, чем если бы вы вручную управляли жизненными циклами. Я уверен, что это зависит от используемой платформы, но проблема, тем не менее, существует. Это также может быть проблематично, если ваш объект содержит ресурсы, подключения к данным и т. Д., Поскольку объект может иногда жить дольше, чем вы ожидаете. Поэтому неизбежно контейнеры IoC имеют тенденцию увеличивать использование ресурсов и использование памяти приложением.

2) Контейнеры IoC, на мой взгляд, являются формой "программирования черного ящика". Я обнаружил, что, в частности, наши менее опытные разработчики склонны злоупотреблять ими. Это позволяет программисту не думать о том, как объекты должны быть связаны друг с другом или как их разъединить, потому что предоставляет им механизм, с помощью которого они могут просто захватывать любой объект из воздуха. Например, может быть веская причина, по которой ObjectA никогда не должен знать о ObjectB напрямую, но вместо создания фабрики, моста или сервисного локатора неопытный программист просто скажет: "Нет проблем, я просто возьму ObjectB из контейнера IoC. ". На самом деле это может привести к усилению связывания объектов, что IoC должен предотвратить.

Лично я использую IoC как своего рода структурную карту своего приложения (да, я также предпочитаю StructureMap;)). Это облегчает замену моих обычных реализаций интерфейса реализациями Moq во время тестов. Создать тестовую настройку можно так же просто, как выполнить новый вызов init для моей IoC-инфраструктуры, заменив какой-либо класс моей границы теста фиктивным.

Вероятно, это не то, для чего нужен IoC, но я использую его чаще всего...

КОНТЕЙНЕР МОК РЕШАЕТ ПРОБЛЕМУ, КОТОРУЮ У ВАС НЕ МОЖЕТ БЫТЬ, НО ЭТО ХОРОШАЯ ПРОБЛЕМА

http://kozmic.net/2012/10/23/ioc-container-solves-a-problem-you-might-not-have-but-its-a-nice-problem-to-have/

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

Другие вопросы по тегам