Данная статья посвящена Inversion of Control (IoC)
контейнеру
Ninject.
Данный IoC
контейнер
имеет легковесный api,
который
позволяет в кратчайшие строки освоить работу с ним. Ninject так же прост в изучении, как и Autofac. А благодаря множеству отдельных библиотек использование Ninject предоставляет все доступные
на сегодняшний день возможности для управления зависимостями. В своей основе Ninject очень прост для того, чтобы, потратив минимальный отрезок времени, его можно было использовать в Enterprise decision (бизнес-решениях). Хотя он не поддерживает interception,
конфигурирования через xml
(для этого нужно скачивать отдельно библиотеки, которых на момент написания статьи
было около 30), asp.new mvc, MVC3, NLog, Glimpse и т.д. Чтобы
продемонстрировать, как использовать данный IoC контейнер, рассмотрим ту же
реализацию электронной библиотеки, которая была использована для рассмотрения
предыдущих IoC
контейнеров в предыдущих статьях (Unity, Autofac, CastleWindsor
и StructureMap).
Диаграмма классов приведена ниже.
Для того, чтобы
показать, как использовать контейнер Ninject, удалим в App.xaml
строчку
для запуска формы в Dependency
Property
(DP) - StartupUri. Для инициализации IoC контейнера
перед стартом программы необходимо перейти в классы App.xaml.cs и
переопределить метод OnStartup. Затем необходимо написать соответствующую
реализацию.
protected override void OnStartup(StartupEventArgs e)
{
var kernel = new StandardKernel();
kernel.Bind<IBook>().To<LibraryBook>();
kernel.Bind<ILibraryBookService>().To<LibraryBookService>();
kernel.Bind<IVisitorRepository>().To<VisitorRepository>();
kernel.Bind<MainViewModel>().ToSelf();
kernel.Bind<MainWindow>().ToSelf();
var model = kernel.Get<MainViewModel>();
var view = kernel.Get<MainWindow>();
view.DataContext = model;
view.Show();
}
Для простых программ
достаточно всего две инструкции:
Bind().To();
Bind().ToSelf();
Но для более сложных можно
использовать дополнительные методы по выбору lifetime manager (менеджер жизни
объектов), инициализацию и использование конструктора на выбор, method injection (MI) и property injection (PI) и многое другое. Ninject также
хорош тем, что он позволяет выполнять конфигурации в отдельных модулях.
Посмотрим, насколько упростится наш пример с использованием модулей (Ninject Module).
public class LibraryModule : NinjectModule
{
public override void Load()
{
Bind<IBook>().To<LibraryBook>();
Bind<ILibraryBookService>().To<LibraryBookService>();
Bind<IVisitorRepository>().To<VisitorRepository>();
Bind<MainViewModel>().ToSelf();
Bind<MainWindow>().ToSelf();
}
}
Использование данного модуля:
var kernel = new StandardKernel(new LibraryModule());
var model = kernel.Get<MainViewModel>();
var view = kernel.Get<MainWindow>();
view.DataContext = model;
view.Show();
К сожалению, базового
функционала, который идет в IoC
контейнере
Ninject,
может быть недостаточно для Вашего приложения, поэтому нужно будет загружать
необходимые дополнения. Ninject
спроектирован
по принципу: базовый функционал плюс дополнительные возможности, которые можно
установить отдельно. Например, если Вы сторонник NHibernate и
конфигурирования через файл конфигурации и не знакомы со Spring.Net (он
отлично подходит для настроек NHibernate),
посмотрите в сторону данного IoC
контейнера.
Пример с NHibernate
был
просто приведен для сравнения, чтобы показать, как дополнительные библиотеки Ninject позволят
ускорить процесс разработки.
Constrain Resolution
Очень часто при
разработке программного обеспечения приходится сталкиваться с тем, что от
одного интерфейса наследуется несколько классов. А класс, который использует
данный интерфейс, должен знать, с каким из вышеперечисленных вариантов его нужно
использовать. Одним из самых простых вариантов решения данной проблемы является
именованная привязка (Named
binding).
Продемонстрируем именованную привязку в электронной библиотеке, которая
использовалась для примеров, добавив класс, который будет отвечать за работу с
домашней коллекцией книг. Назовем этот класс HomeLibraryBookService, который
наследуем от интерфейса ILibraryBookService. Ниже будет приведена тестовая
реализация для демонстрации использования данного класса. В списке источников в
конце статьи Вы можете скачать исходные коды к данной статье и ознакомится
более подробно с использованием Ninject.
public class HomeLibraryBookService : ILibraryBookService
{
#region
Variable
private ObservableCollection<IBook> _books;
#endregion
#region
Constructor
public HomeLibraryBookService()
{
_books = new ObservableCollection<IBook>();
_books.Add(new LibraryBook { Author = "Test1", Title = "Test
Unity1",
Count = 3, SN = "ISBN: 9781617291340", Year = new DateTime(2013, 9, 10) });
_books.Add(new LibraryBook { Author = "Test2", Title = "Test
Unity2",
Count = 2, SN = "ISBN-10: 0201485672", Year = new DateTime(1999, 7, 8) });
}
#endregion
#region
Public Methods
public void GetData(Action<ObservableCollection<IBook>, Exception> callback)
{
callback(_books, null);
}
public IBook FindBook(IBook findBook)
{
if (findBook == null)
return null;
return _books.FirstOrDefault(book =>
book.Author == findBook.Author
&&
book.Title == findBook.Title
&&
book.SN == findBook.SN
&&
book.Year == findBook.Year);
}
public void CreateNewBook()
{
_books.Add(new LibraryBook { Author = "UnityTest1", Title = "Test1", Count = 5, SN = "ISBN-10:
0735667454", Year = DateTime.Now
});
}
public void RemoveBook(IBook book)
{
if (book == null)
return;
_books.Remove(book);
}
#endregion
}
Интерфейс ILibraryBookService используется в модели
представления MainViewModel, которая отвечает за связывание моделей с
представлением для проекта “Электронная библиотека”. Реализация представлена
ниже.
public class MainViewModel : NotifyModelBase
{
private readonly ILibraryBookService _libraryDataService;
public MainViewModel(ILibraryBookService dataService)
{
_libraryDataService = dataService;
_libraryDataService.GetData(
(items, error) =>
{
Books = items;
});
}
}
С данного
класса для был приведен только необходимый код для модифицированного примера использования. Посмотрим, как изменился класс LibraryModule со внесением именованной привязки.
public class LibraryModule : NinjectModule
{
public override void Load()
{
Bind<IBook>().To<LibraryBook>();
Bind<ILibraryBookService>().To<HomeLibraryBookService>().Named("HomeLibrary");
Bind<ILibraryBookService>().To<LibraryBookService>().Named("Library");
Bind<IVisitorRepository>().To<VisitorRepository>();
Bind<MainViewModel>().ToSelf();
Bind<MainWindow>().ToSelf();
}
}
К сожалению,
сразу после внесения проект не запустится, так как нужно указать, какой класс
будет использоваться при constructor injection в классе MainViewModel. Поэтому в данном классе изменим немного вызов конструктора.
public
MainViewModel([Named("Library")]ILibraryBookService
dataService)
В данном
примере был рассмотрен один из самых простых способов решения ограничения по
привязке.
Другим
способом является ограничения связывания с помощью использования ConstraintAttribute. Поскольку в примере с электронной библиотекой сложно представить, как использовать данный подход, возьмем пример, предоставленный разработчиками Ninject.
// will work just as well without
this line, but it's more correct and important for IntelliSense etc.
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter,
AllowMultiple = true, Inherited = true)]
public class Swimmer : ConstraintAttribute
{
public bool Matches(IBindingMetadata metadata)
{
return metadata.Has("CanSwim") &&
metadata.Get<bool>("CanSwim");
}
}
class WarriorsModule : Ninject.Modules.NinjectModule
{
public override void Load()
{
Bind<IWarrior>().To<Ninja>();
Bind<IWarrior>().To<Samurai>().WithMetadata("CanSwim", false);
Bind<IWarrior>().To<SpecialNinja>().WithMetadata("CanSwim", true);
}
}
class AmphibiousAttack
{
public AmphibiousAttack([Swimmer]IWarrior
warrior)
{
Assert.IsType<SpecialNinja>(warrior);
}
}
Итоги
Ninject -
это
мощный и одновременно легкий в использовании IoC контейнер.
По простоте использования, а также понятности api этот
контейнер чем-то напоминает Autofac.
Данный контейнер имеет очень мощную поддержку в онлайн-обществе. Пожалуй,
единственным нюансом, который может смущать в данном IoC контейнере,
-
это скорость его работы. Почему-то этот контейнер для управления зависимостями
уступает по скорости всем описанным мной ранее IoC контейнерам. Но если у Вас
небольшое приложение, и Вам не нужно создавать много объектов через данный
контейнер, то в плане простоты этот контейнер управления зависимостями -
именно то, что нужно.
Список
литературы:
No comments:
Post a Comment