8 апр. 2010 г.

Dependency Injection на примере с Autofac

Доброе время суток!
В этом посте мы на примере разберем понятие dependency injection (внедрение зависимости).
Для начала хочу рассказать по своему опыту. У меня был проект, довольно сложный. В погоне за правильной архитектурой приложения я разбил проект на модули. Проект заработал и был запущен в production. Все бы было хорошо, пока дело не дошло до внесения изменений в функционал/код. Большие зависимости между классами вызывали трудности при тестировании и внесении изменений в код. На моей электронной полке к тому времени уже давно лежала книжка Dhanji R. Prasanna Dependency Injection.
Т.к. проект у меня был на .NET я начал искать IoC контейнеры в реализации для этой платформы.

Найдены были:
Почему Windsor на первом месте ? :) Потому что как-то давно, я уже делал на нем простые примеры. Но сейчас мой выбор пал на AutoFac. Аргументы ? Во-первых проект очень быстро развивается. Уже сейчас, еще до выхода .NET Framework 4.0, уже есть поддержка последнего фреймворка. Это не может не радовать! Во-вторых уже сейчас в AutoFac есть поддержка WCF, MEF, ASP.NET MVC. Поддержку MEF выделяю особенно. Ну и в-третьих мне понравилась архитектура библиотеки, все ясно и понятно.

И так, рассмотрим работу AutoFac на небольшом примере. Для начала создадим консольный проект. В этом же solution создадим class library и назовем её IfaceLib. В этой библиотеке опишем 2 интерфейса.

public interface ILibOne
{
string getMyName();
}
public interface ILibTwo
{
string getMyFullName();
}

Теперь создадим еще одну class library и назовем её LibOne. В ней мы реализуем интерфейс ILibOne в классе LibOneClass. Перед этим не забудем зареференсить сборку IfaceLib.

public class LibOneClass : ILibOne
{
public string getMyName()
{
return "Spectrum";
}
}

Создадим еще одну class library и назовем её LibTwo. В ней мы реализуем интерфейс ILibTwo в классе LibTwoClass.

public class LibTwoClass : ILibTwo
{
ILibOne one;

public LibTwoClass(ILibOne one)
{
this.one = one;
}

public string getMyFullName()
{
return "ZX " + one.getMyName();
}
}

Как мы видим LibTwoClass явно использует метод getMyName() класса LibOneClass через интерфейс. Но так же LibTwoClass ничего не подозревает, о существовании LibOneClass. Этого мы и добивались.
Теперь приступим к реализации самой программы, использующей эти классы.

class Program
{
static void Main(string[] args)
{
var builder = new ContainerBuilder();
builder.RegisterType<LibOneClass>().As<ILibOne>();
builder.RegisterType<LibTwoClass>().As<ILibTwo>();
builder.Register(c => new LibTwoClass(c.Resolve<ILibOne>()));
var container = builder.Build();

var two = container.Resolve<ILibTwo>();
Console.WriteLine(two.getMyFullName());
Console.ReadLine();
}
}

Разберем что у нас происходит в коде программы.
Для начала мы создаем контейнер:

var builder = new ContainerBuilder();

Регистрируем типы:

builder.RegisterType<LibOneClass>().As<ILibOne>();
builder.RegisterType<LibTwoClass>().As<ILibTwo>();

Далее мы определяем поведение контейнера, при создании LibTwoClass. Ведь в этом классе есть зависимость от LibOneClass. В следующей строке мы указываем контейнеру, как ее разрешить:

builder.Register(c => new LibTwoClass(c.Resolve<ILibOne>()));

Таким образом, при создании LibTwoClass контейнер автоматически разрешит зависимость, и подставит заместо ILibOne экземпляр LibOneClass.
В результате выполнения программы на экране мы получим:

ZX Spectrum

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

На этом у меня пока все. В следующем топике постараюсь рассказать про использование mock-объектов.