0,0
рейтинг
6 ноября 2011 в 19:20

Разработка → IoC, DI, IoC-контейнер — Просто о простом из песочницы

.NET*
Думаю сейчас слова IoC, DI, IoC-контейнер, как минимум у многих на слуху. Одни этим активно пользуются, другие пытаются понять, что же это за модные веяния.

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

Теория


Для меня взаимосвязь между IoC и DI такая же как между Agile и Scrum, т.е.
Inversion of Control (инверсия управления) — это некий абстрактный принцип, набор рекомендаций для написания слабо связанного кода. Суть которого в том, что каждый компонент системы должен быть как можно более изолированным от других, не полагаясь в своей работе на детали конкретной реализации других компонентов.
Dependency Injection (внедрение зависимостей) — это одна из реализаций этого принципа (помимо этого есть еще Factory Method, Service Locator).
IoC-контейнер — это какая-то библиотека, фреймворк, программа если хотите, которая позволит вам упростить и автоматизировать написание кода с использованием данного подхода на столько, на сколько это возможно. Их довольно много, пользуйтесь тем, чем вам будет удобно, я продемонстрирую все на примере Ninject.

Практика


Согласно подходу инверсии управления если у нас есть клиент, который использует некий сервис, то он должен делать это не напрямую, а через посредника, своего рода аутсорсинг.
Схема взаимодействия клиента с сервисом
То как технически это будет сделано и определяет каждая из реализаций подхода IoC.
Мы будем использовать DI, на простом примере:
Скажем есть некий класс, который создает расписание, а другой класс его отображает (их как правило много, скажем один для десктоп-приложения, другой для веба и т.д.).
Если бы мы ничего не знали о IoC, DI мы бы написали что-то вроде этого:
class ScheduleManager
{
	public Schedule GetSchedule()
	{
		// Do Something by init schedule...   
	}
}

class ScheduleViewer
{
	private ScheduleManager _scheduleManager = new ScheduleManager();
	public void RenderSchedule()
	{
		_scheduleManager.GetSchedule();
		// Do Something by render schedule... 
	}
}

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

Мы, например, воспользуемся внедрением зависимостей (DI) для того, чтобы разорвать этот клубок стальных ниток — сделаем связь между этими классами более слабой, добавив прослойку в виде интерфейса IScheduleManager. И будем разрешать ее одним из способов техники DI, а именно Constructor Injection (помимо этого есть Setter Injection и Method Injection — если в двух словах, то везде используется интерфейс вместо конкретного класса, например в типе свойства или в типе аргумента метода):
interface IScheduleManager
{
	Schedule GetSchedule();
}

class ScheduleManager : IScheduleManager
{
	public Schedule GetSchedule()
	{
		// Do Something by init schedule...        
	}
}

class ScheduleViewer
{
	private IScheduleManager _scheduleManager;
	public ScheduleViewer(IScheduleManager scheduleManager)
	{
		_scheduleManager = scheduleManager;
	}
	public void RenderSchedule()
	{
		_scheduleManager.GetSchedule();
		// Do Something by render schedule... 
	}
}

И далее там где мы хотим воспользоваться нашим классом для отображения расписания мы пишем:
ScheduleViewer scheduleViewer = new ScheduleViewer(new ScheduleManager()); 

Вот уже почти идеально, но что если у нас много различных ScheduleViewer, разбросанных по проекту, которые использует всегда именно ScheduleManager (придется его руками каждый раз создавать) и/или мы хотим как-либо настроить поведение, так что бы в одной ситуации везде использовать ScheduleManager, а в другой скажем AnotherScheduleManager и т.д.
Решить эту проблему как раз и призваны IoC-контейнеры.

IoC-контейнеры


Они помогают уменьшить количество рутины, позволяя задать соответствие между интерфейсом и его конкретной реализацией, чтобы потом везде этим пользоваться.
Как я уже говорил выше, мы будем рассматривать это на примере Ninject —
1. Сначала мы создаем конфигурацию контейнера:
class SimpleConfigModule : NinjectModule
{
	public override void Load()
	{
		Bind<IScheduleManager>().To<ScheduleManager>();
		// нижняя строка необязательна, это поведение стоит по умолчанию:
		// т.е. класс подставляет сам себя
		Bind<ScheduleViewer>().ToSelf();
	}
}

Теперь везде где требуется IScheduleManager будет подставляться ScheduleManager.
2. Создаем сам контейнер, указывая его конфигуратор:
IKernel ninjectKernel = new StandardKernel(new SimpleConfigModule());
// там где нужно создать экземпляр ScheduleViewer мы вместо new, делаем так:
ScheduleViewer scheduleViewer= ninjectKernel.Get<ScheduleViewer>();

Контейнер сам создаст экземпляр класса ScheduleManager, вызовет конструктор ScheduleViewer и подставит в него свежесозданный экземпляр ScheduleManager.

Заключение


И напоследок хочется сказать, что IoC, DI — очень хороший инструмент, но и как любой другой механизм использовать его нужно осознанно и к месту. Скажем, одно дело какое-нибудь небольшое консольное приложение, в котором вряд ли что-то будет меняться, или серьезный крупный проект, где пожелания заказчика часто изменчивы и противоречивы.
Вот и все, буду очень рад услышать ваши комментарии, конструктивные замечания.
Удачного всем деплоя на продакшене.
Ринат Муллаянов @RinatMullayanov
карма
1,0
рейтинг 0,0
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Спецпроект

Самое читаемое Разработка

Комментарии (28)

  • 0
    действительно очень как-то просто
  • +1
    Замечания?! :) Концовка статьи откуда взята? Форматирование надо привести к нормальному виду!

    По ninject вообще странности, вы какую версию используете?!

    Bind().To(); Что и с чем связывается то?!
    ninjectKernel.Get(); Такой перегрузки то же нет в ninject!

    Да даже если используется версия, которая мне не знакома, сложно поверить, что используется какое-то «умный» механизм, который сам умеет разрешать все зависимости интерфейсов с классами без явных указаний их в коде или хотя бы в конфигурационном файле.
    • 0
      в превью было нормально написано
    • 0
      а в публикации код поломался
    • +1
      исправляю
      • 0
        А теперь убрать лишнее под кат. :)
    • 0
      Использую Ninject версия 2.2.1.4
      • 0
        Да версия тут не к чему, вот код в порядок привели и уже ясно что это нормальный ninject :)
    • 0
      концовка статьи взята из своей головы
  • +5
    Вы бы хоть превью нажали-посмотрели.
  • +3
    Товарищ Автор, нельзя ли сократить то, что выводится на главной странице?
  • 0
    я написал <habracut/> текст первого абзаца
    в чем проблема, подскажите?
  • 0
    все понял и исправил
  • 0
    Спасибо, познавательно.
  • 0
    Осталось дождаться, когда под .Net аналог Seam напишут :)
    • 0
      Какой-такой Seam? И при чем он тут, в топике про DI и IoC?
      • 0
        Мной подразумевался JBoss Seam, а точнее Seam Weld. Weld — реализация IoC-контейнера для JavaEE. Среди прочего — умеет выполнять DI даже без выделения интерфейса, поддерживает такую приятную фичу как квалификаторы и прочее.
    • 0
      в статье шел разговор только про DI и IoC и ничего более. я конечно, не работал со Seam, однако для разработки веб-приложений с TDD и DDD прекрасно подходит ASP.NET MVC. сам использую и Ninject вместе с ним. а вместо POJO прекрасно подходят POCO из Entity Framework 4.
      • 0
        К слову, POCO на то и POCO, что они не «из EF 4» или еще откуда то. Это просто объекты, которые только содержат логику или являются DTO, но понятия не имеют кто и как их хранит.
        • 0
          абсолютно согласен, однако возможность работы с ними есть именно в EF 4, но не в LINQ to SQL, например.
          • 0
            Ну в том то и дело, что не только в EF4. Старый добрый NHibernate, например. У него эта возможность была с доисторических времен :)
    • 0
      Лучше бы похронили этот чёртов Seam…
  • 0
    Нужно было еще рассказать про LifetimeManager. Раз мы не используем new для создания объекта, то можем использовать уже созданный. И можем реализовать, например, синглтон таким образом.
    • 0
      будет время напишу
  • 0
    В свое время выбирал подходящий IoC-контейнер, остановился на Autofac
  • +1
    Мне кажется, что перед тем как писать пост, стоит посмотрел, актуален ли он:

    Пролистайте следующие ссылки:

    habrahabr.ru/blogs/net/62830/
    habrahabr.ru/blogs/net/50845/
    habrahabr.ru/blogs/net/63568/
    habrahabr.ru/blogs/net/91650/
    habrahabr.ru/blogs/complete_code/116232/
    habrahabr.ru/blogs/net/53922/
    • 0
      а мне кажется тебе даже не стоило об этом беспокоится и вообще обращать СВОЕ внимание на статью

Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.