Sunday, December 1, 2013

Использование Castle Windsor

Эта статья является продолжением серии статей по IoC контейнерах. В материале рассмотрим такой IoC контейнер как Castle Windsor. (Этимология названия: Виндзо́рский за́мок – незыблемый символ британской монархии и резиденция ее представителей на протяжении более 900 лет в городе Виндзор, Англия.) В силу отсутствия информации о причинах столь оригинального названия разработчиками своего «детища», буду рад, если Вы сможете познакомить меня с источником его происхождения.
Пожалуй, начнем с диаграммы классов. В предыдущих моих примерах, где описывались  IoC контейнеры  Autofac и Unity, была приведена полная реализация интерфейсов и классов, которые имплементировали эти интерфейсы. Это занимало много места в статье и не давало пользователю уловить то, что я пытался ему донести. Сейчас я решил изобразить с помощью Visio диаграмму классов (без ассоциаций, привязок и т.д.) чтобы показать, как выглядит структура проекта. Основная модель представления представлена классом MainViewModel, в котором можно увидеть на диаграмме классов ниже.

В классе MainViewModel реализованы все виды dependency injection (DI):
  • Constructor injection (CI) – внедрение зависимостей через конструктор;
  • Method injection (MI) – внедрение зависимости через аргумент функции;
  • Property injection (PI) – внедрение зависимостей через проперти.  
Рассмотрим, как будет реализован самый простой сценарий constructor injection для класса MainViewModel, чтобы посмотреть на Castle Windsor в действии. Чтобы инициализировать IoC контейнер перед стартом программы, необходимо перейти в классы App.xaml.cs и переопределить метод OnStartup.  Затем необходимо написать соответствующую реализацию, которая может выглядеть следующим образом:
protected override void OnStartup(StartupEventArgs e)
{
    using (var container = new WindsorContainer())
    {
        container.Register(Component.For<ILibraryBookService>()
            .ImplementedBy<LibraryBookService>()
            .Named("LibraryBookService"));
        container.Register(Component.For<ILibraryBookService>()
            .ImplementedBy<HomeLibraryBookService>()
            .Named("HomeLibraryBookService"));
        container.Register(Component.For<IBook>()
                                    .ImplementedBy<LibraryBook>());
        container.Register(Component.For<IVisitorRepository>()
                                    .ImplementedBy<VisitorRepository>());
        container.Register(Component.For<MainViewModel>());
        var model = container.Resolve<MainViewModel>();
        var view = new MainWindow {DataContext = model};
        view.Show();
    }
}
После запуска программы мы увидим, что программа работает и все интерфейсы и классы связаны и проинициализированы. IoC контейнер Castle Windsor основывается на паттерне RRR (Register, Resolve, Release), который неоднократно упоминается в книге Dependency Injection in .NET.
Register представляет собой привязку интерфейсов и классов. Может использоваться несколько подходов для начальной настройки и инициализации IoC контейнера:
  1. Через код с использованием метода Register;
  2. Через конфигурационный файл;
  3. С использованием класса IWindsorInstaller.
Resolver позволяет использовать сконфигурированный WindsorContainer и доставать из него необходимые данные с помощью методов Resolve<> и ResolveAll<>.
Release позволяет зачистить контейнер после использования. Контейнер управляет временем жизни службы компонентов, и прежде чем мы выключим наше приложение, нужно отключить контейнер, который, в свою очередь, отпишется от всех ссылок на компоненты, которыми он управляет (например, утилизировать их через метод Dispose). Вот почему действительно важно вызвать container.Dispose(), прежде чем закрыть приложение.
В данной статье будут рассмотрены все три похода для того, чтобы показать возможности работы этого контейнера.  Castle Windsor – очень мощный IoC контейнер. Он поддерживает сканирование сборок, как это делает Autofac, и может автоматически регистрировать интерфейсы и классы в данных сборках.  Он также поддерживает AOP (Аспе́ктно-ориенти́рованное программи́рование (АОП)  парадигма программирования, основанная на идее разделения функциональности для улучшения разбиения программы на модули) с помощью Interceptors (как, например, это делается в том же Unity). Если связать на выхлопе все возможности этого контейнера, то получим достаточно мощный продукт, которым позволит Вам управлять зависимостями.
Продолжим конфигурировать данный IoC контейнер. Настройку этого контейнера через конфигурационный файл отложим на время и возьмемся за конфигурирование через IWindsorInstaller. С помощью IWindsorInstaller контейнер Castle Windsor поддерживает модульность. Если в архитектуре вашего программного обеспечения хорошо прослеживается модульность (например, Вы используете подход domain-driven design), то эта возможность позволит Вам настраивать каждый модуль отдельно, а затем просто и легко это сконфигурировать. Посмотрите, как это будет выглядеть:
public class LibraryServiceInstaller : IWindsorInstaller
{
    public void Install(Castle.Windsor.IWindsorContainer container, Castle.MicroKernel.SubSystems.Configuration.IConfigurationStore store)
    {
        container.Register(Component.For<ILibraryBookService>()
            .ImplementedBy<LibraryBookService>()
            .Named("LibraryBookService"));
        container.Register(Component.For<ILibraryBookService>()
            .ImplementedBy<HomeLibraryBookService>()
            .Named("HomeLibraryBookService"));
        container.Register(Component.For<IBook>()
                                    .ImplementedBy<LibraryBook>());
        container.Register(Component.For<IVisitorRepository>()
                                    .ImplementedBy<VisitorRepository>());
        container.Register(Component.For<MainViewModel>());
    }
}
Реализация в App.xaml.cs будет иметь такой вид:
protected override void OnStartup(StartupEventArgs e)
{
    using (var container = new WindsorContainer())
    {
        container.Install(new LibraryServiceInstaller());
        var model = container.Resolve<MainViewModel>();
        var view = new MainWindow {DataContext = model};
        view.Show();
    }
}
Для опытных разработчиков этот код не нуждается в объяснениях. Для начинающих же программистов дам некоторые комментарии-нюансы насчет первого примера конфигурации данного IoC контейнера.
Метод Register для WindsorContainer позволяет зарегистрировать необходимые привязки. Он принимает на вход как параметр интерфейс IRegistration.
IWindsorContainer Register(params IRegistration[] registrations)
Для связывания интерфейсов и классов Castle Windsor использует термин service (сервис). Не будем отходить от данной терминологии и будем также ее использовать.
Метод Component.For<> используется для регистрации компонента и дальнейшего его использования как типа сервиса (service type). Если возьмем приведенный выше пример, то для кода
container.Register(Component.For<ILibraryBookService>()
            .ImplementedBy<LibraryBookService>()
            .Named("LibraryBookService"));
его использование будет выглядеть так:
var library = container.Resolve<ILibraryBookService>();
а реализация
var library = container.Resolve<LibraryBookService>();
будет запрещена.
Метод ComponentRegistration<TService> ImplementedBy<TImpl>() where TImpl : TService позволяет установить конкретный тип, который реализует TImpl. Проще говоря, позволяет привязать интерфейс к реализации.
Метод ComponentRegistration<TService> Named(string name) используеться для именованной регистрации. Это делается для того, чтобы можно было достать нужный тип с контейнера по имени.
container.Install(new LibraryServiceInstaller());
Используется для того, чтобы инкапсулировать логику по конфигурированию данного IoC контейнера в отдельный файл. Позволяет гибко настраивать структуру самого приложения.  
Рассмотрим сейчас вариант использования для конфигурирования IoC контейнера файл конфигурации, а затем перейдем к примерам с использованием property injection и method injection.
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <configSections>
    <section
        name="castle"
        type="Castle.Windsor.Configuration.AppDomain.CastleSectionHandler, Castle.Windsor" />
  </configSections>
  <castle>

    <components>
      <!-- IBook-->
      <component
          id="LibraryBook"
          service="CastleWindsorDemo.Model.IBook, CastleWindsorDemo"
          type="CastleWindsorDemo.Model.LibraryBook, CastleWindsorDemo" />

      <component
          id="LibraryBookService"
          service="CastleWindsorDemo.Model.ILibraryBookService, CastleWindsorDemo"
          type="CastleWindsorDemo.Model.LibraryBookService, CastleWindsorDemo" />

      <component
          id="HomeLibraryBookService"
          service="CastleWindsorDemo.Model.ILibraryBookService, CastleWindsorDemo"
          type="CastleWindsorDemo.Model.HomeLibraryBookService, CastleWindsorDemo" />

      <component
          id="Visitor"
          type="CastleWindsorDemo.Model.Visitor, CastleWindsorDemo" />

      <component
          id="VisitorRepository"
          service="CastleWindsorDemo.Model.IVisitorRepository, CastleWindsorDemo"
          type="CastleWindsorDemo.Model.VisitorRepository, CastleWindsorDemo" />
     
      <component
          id="MainViewModel"
          type="CastleWindsorDemo.ViewModel.MainViewModel, CastleWindsorDemo" />

    </components>

  </castle>
</configuration>
Использование файла конфигурации будет выглядеть следующим образом:
using (var container = new WindsorContainer(new XmlInterpreter()))
{
    var model = container.Resolve<MainViewModel>();
    var view = new MainWindow {DataContext = model};
    view.Show();
}
Использовать xml конфигурацию IoC контейнера Castle Windsor очень просто. Нам пришлось только изменить создание контейнера, чтобы он умел работать с конфигурационный файлом. Можно немного переписать файл конфигурации для того, чтобы использовать написанный ранее LibraryServiceInstaller. Для этого закомментируем или удалим  тег <components> и добавим тег <installers>.
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <configSections>
    <section
        name="castle"
        type="Castle.Windsor.Configuration.AppDomain.CastleSectionHandler, Castle.Windsor" />
  </configSections>
  <castle>
    <installers>
      <install
          type="CastleWindsorDemo.Modules.LibraryServiceInstaller, CastleWindsorDemo"/>
      <install assembly="CastleWindsorDemo"/>
    </installers>
   
  </castle>
</configuration>
Запускаем проект и видим результат с использованием LibraryServiceInstaller.

Рассмотрим, как работает Property Injection в Castle Windsor. Ответственность в определении того, какие свойства используются для инъекций, выполняется по умолчанию через PropertiesDependenciesModelInspector – реализации IContributeComponentModelConstruction, который использует все следующие критерии для определения того, как свойство представляет зависимость:
  • Имеет "public" доступный сеттер;
  • Является свойством экземпляра класса;
  • Если ComponentModel.InspectionBehavior установлен в PropertiesInspectionBehavior.DeclaredOnly, не наследуется;
  • Не имеет параметров;
  • Не помечен атрибутом Castle.Core.DoNotWireAttribute.
Интересная новость заключается в том, что если ваше свойство подпадает под перечисленные выше 5 пунктов, то оно автоматически инициируется и связывается с нужной реализацией. Это все за вас делает Castle Windsor.
Пример как будет выглядеть method injection в Castle Windsor.
using (var container = new WindsorContainer(new XmlInterpreter()))
{
    var model = container.Resolve<MainViewModel>();
    var view = new MainWindow {DataContext = model};
    model.PrintBook(container.Resolve<ILibraryBookService>("HomeLibraryBookService"));
    view.Show();
    container.Release(view);
}
Итоги
В данной статье я провел краткий анализ использования IoC контейнера Castle Windsor для управления зависимостями. Этот контейнер имеет очень много возможностей, которые не вошли в данную статью из-за их обширности: Dynamic Proxy (interceptors), Typed Factory Facility (для работы с фабриками) и много других возможностей. Надеюсь, что материал поможет разобраться с основными нюансами, которые необходимы для начала работы с этим контейнером.

Источники:

No comments:

Post a Comment