Тема DI/IoC достаточно простая, но в сети очень сложно найти хорошее описание того, как это работает и зачем это нужно. Вот моя попытка, с использованием Unity. Хорошо ли объяснена тема – судить вам.
Давайте создадим простенький пример:
Если коротко, то наша проблема – это
Как? Этим-то как раз и занимаются паттерны IoC и DI. IoC (Inversion of Control) – это паттерн, в котором управление объектом (в нашем случае – временем жизни объекта) поручено какой-то компоненте. Некий такой аутсорс – вместо того чтобы создавать объект самим (через
Правда каждый раз просить копию объекта у контейнера бывает лень. В этих случаях мы можем воспользоваться другим паттерном. DI (Dependency Injection) позволяет нам автоматически вытянуть из контейнера нужные нам зависимости при инициализации. То есть, когда мы создаем
Воспользуемся фреймкорком Unity для нашей программы. Для начала, перепишем
Вот и все! Спасибо за внимание! Если вас заинтересовала тема и вы живете в Петербурге, приходите к нам завтра на встречу.
Читать 2ю часть серии
Давайте создадим простенький пример:
// сервис. содержит бизнес-логику
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ю часть серии