Тема 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ю часть серии