Как стать автором
Обновить

DI и IoC для начинающих

.NET *
Тема DI/IoC достаточно простая, но в сети очень сложно найти хорошее описание того, как это работает и зачем это нужно. Вот моя попытка, с использованием Unity. Хорошо ли объяснена тема – судить вам.


Давайте создадим простенький пример:

// сервис. содержит бизнес-логику
public class MyService<br/>
{<br/>
  public MyService()<br/>
  {<br/>
    ⋮<br/>
  }<br/>
  public void DoSomething()<br/>
  {<br/>
    ⋮<br/>
  }<br/>
}<br/>
 <br/>
// окошко. пользуется бизнес-логикой
public class MyWindow : Form<br/>
{<br/>
  private MyService service;<br/>
  public MyWindow()<br/>
  {<br/>
    service = new MyService();<br/>
  }<br/>
}<br/>
 <br/>
// програмка которая показывает окошко
public class MyProgram<br/>
{<br/>
  static void Main()<br/>
  {<br/>
    ⋮<br/>
    /// создаем окно
    Application.Run(new MyWindow);<br/>
  }<br/>
}<br/>
В этом коде одна проблема: класс MyWindow слишком сильно привязан к классу MyService. Это плохо потому что

  • Не получится написать unit-тест для MyWindow в отрыве от MyService. Фактически получится интеграционный тест.
  • Невозможно заменить MyService на OtherService, если только OtherService от него не наследует. Если от MyService зависят несколько классов, придется менять их все.
  • Если наш сервис требует настройки, его придется настраивать в каждом классе, который его использует.

Если коротко, то наша проблема – это operator new(). Чтобы грамотно контролировать зависимости и позволить себе тестировать объекты в изоляции, от этого оператора нужно отказаться.

Как? Этим-то как раз и занимаются паттерны IoC и DI. IoC (Inversion of Control) – это паттерн, в котором управление объектом (в нашем случае – временем жизни объекта) поручено какой-то компоненте. Некий такой аутсорс – вместо того чтобы создавать объект самим (через new()) мы запрашиваем его у т.н. IoC-контейнера, то есть у фабрики, которая умеет грамотно производить объекты.

Правда каждый раз просить копию объекта у контейнера бывает лень. В этих случаях мы можем воспользоваться другим паттерном. DI (Dependency Injection) позволяет нам автоматически вытянуть из контейнера нужные нам зависимости при инициализации. То есть, когда мы создаем MyWindow через IoC-контейнер, механизм DI магическим способом сможет инициализировать MyService без нашего прямого участия.

Как это работает?


Воспользуемся фреймкорком Unity для нашей программы. Для начала, перепишем Main() – создадим в нем контейнер и применим DI к нашему окошку:

public class MyProgram<br/>
{<br/>
  static void Main()<br/>
  {<br/>
    ⋮<br/>
    var uc = new UnityContainer();<br/>
    Application.Run(uc.Resolve<MyWindow>());<br/>
  }<br/>
}<br/>
Методом Resolve() контейнера, мы запрашиваем не только создание объекта типа MyWindow, но и автоматической создание всех его зависимостей. Теперь посмотрим на то, как можно получить создание сервиса (т.е. зависимой части) автоматически. Для начала, вытащим интерфейс сервиса, чтобы его можно было в последствии поменять:

interface IService<br/>
{<br/>
  void DoSomething();<br/>
}<br/>
 <br/>
public class MyService : IService<br/>
{<br/>
  ⋮ // все как и раньше
}<br/>
Теперь меняем MyWindow чтобы использовался именно интерфейс. Есть несколько вариантов того, как можно добавить ссылку на сервис чтобы контейнер его инициализировал. Вот один из них:

public class MyWindow : Form<br/>
{<br/>
  private IService service;<br/>
  public MyWindow(IService service)<br/>
  {<br/>
    this.service = service;<br/>
  }<br/>
}<br/>
Теперь осталось сделать только одно – сказать контейнеру чтобы по запросу объектов типа IService он выдавал MyService:

public class MyProgram<br/>
{<br/>
  static void Main()<br/>
  {<br/>
    ⋮<br/>
    var uc = new UnityContainer();<br/>
    uc.RegisterType<IService, MyService>();<br/>
    Application.Run(uc.Resolve<MyWindow>());<br/>
  }<br/>
}<br/>
Вот и все!!! Теперь при запуске программы переменная service окошка будет инициализирована автоматически. Это называется «инъекция в конструктор» (constructor injection). Хотите оставить конструктор пустым? Пожалуйста:

public class MyWindow : Form<br/>
{<br/>
  [Dependency]<br/>
  public IService Service { get; set; }
<br/>
 <br/>
  public MyWindow()<br/>
  {<br/>
    ⋮<br/>
  }<br/>
}<br/>
Сменив поле на свойство и пометив его аттрибутом [Dependency], мы намекнули контейнеру, что его нужно инициализировать при создании класса. Результат тот же, что и с конструктором. Такой прием называется «инъекция в свойство» (setter injection).

Вот и все! Спасибо за внимание! Если вас заинтересовала тема и вы живете в Петербурге, приходите к нам завтра на встречу.

Читать 2ю часть серии
Теги:
Хабы:
Всего голосов 44: ↑35 и ↓9 +26
Просмотры 121K
Комментарии Комментарии 46