Страницы с тегами : IoC, IoC-контейнер

Статьи про IoC, AOP и контейнер Unity

Первые посты про IoC я написал более двух лет назад, но судя по статистике блога люди часто к ним обращаются.

В этом после приведу индекс статей в порядке чтения для облегчения поиска и навигации

  1. Введение в IoC
  2. Основная задача IoC-контейнеров
  3. Первые шаги с Unity
  4. Рефакторинг legacy-кода для использования Unity
  5. Использование generic-классов с Unity
  6. Инъекция массивов в Unity
  7. Unity LifetimeManager
  8. Конфигурация Unity
  9. Инъекция самого контейнера Unity в объекты
  10. AOP в Unity
  11. Фабрики в Unity
  12. Практика AOP: аудит изменений данных в EF с учетом котнекста операций
  13. Unity 2.0
  14. AOP в Unity 2.0


Unity 2.0

Давненько я не писал про Unity. За это время успела выйти новая версия этого IoC-контейнера и появилась новая версия Enterprise Library.

Первое заметное изменение в Unity: теперь требуется подключать одну сборку Microsoft.Practices.Unity. Хотя AOP для Unity все еще лежит в отдельной сборке Microsoft.Practices.Unit.Interception.

Интерфейс класса UnityContainer расширился методами IsRegistered и свойством Registrations. Последнее позволяет анализировать что мы уже положили в контейнер.

Три больших изменения в резолвинге. 

  1. Теперь можно резолвить Func<T>, который будет возвращать зарегистрированный в контейнере экземпляр T. Аналогично можно резолвить Func<IEnumerable<T>>, который будет возвращать все зарегистрированные в контейнере именованные зависимости типа T. Это дает возможность переконфигурировать контейнер во время работы программы, так чтобы остальной код об этом не знал.
  2. При наличии нескольких конструкторов в создаваемом классе по-умолчанию выбирается конструктор с максимальным количеством параметров.
  3. В метод Resolve теперь можно передавать объекты ResolverOverride, позволяющие переопределять поведение Unity при резолвинге.

Добавились два LifetimeManager. HierarchicalLifetimeManager – чтобы дочерние контейнеры резолвили свои экземпляры, а не брали родительский. Это может пригодиться в вебе при создании дочерних контейнеров на каждый запрос. PerResolveLifetimeManager – позволяет переиспользовать один экземпляр в пределах одного вызова метода Resolve.

Подробнее можно почитать в Change Log.

PS. Также есть версия для Silverlight.

PPS. Качать здесь.



Аудит изменений с учетом контекста операций

В предыдущем посте я описал способ, который позволяет проводить аудит изменений данных в Entity Framework, не затрагивая сами классы сущностей.

Аудит изменений данных вполне возможно делать в самой БД с помощью триггеров, но в базу не попадают сведения о том какой пользователь системы произвел изменения (в случае приложения, построенного по принципу trusted subsystem) и с какой целью были проведены эти изменения.

Для каждой операции работы изменения данных существует некоторый контекст, который включает в себя как некоторые явные параметры, как имя пользователя или URL, так и неявные, например намерения, с которыми были выполнены изменения данных (частично их можно восстановить по стеку вызовов).

Для задач логирования было бы удобно иметь доступ к некоторым параметрам контекста, особенно неявным. Эти параметры должны прозрачно передаваться по цепочке вызовов. Для решения таких задач можно применить монады, но тогда придется переписать весь код под использование монад, что очень проблематично. Можно воспользоваться возможностями AOP и IoC чтобы обеспечить неявную передачу явного контекста.

Для начала нам понадобится класс для хранения текущего контекста. Как и в случае со стеком вызовов, контексты тоже могут составлять некоторый стек.

public interface IContextManager<T>
{
    void Push(T value);
    void Revert();
    IEnumerable<T> GetValues();
}


MEF

MEF – Managed Extensibility Framework – новая библиотека для создания композитных приложений. То есть приложений, которые собираются из отдельных частей.

Скачать его можно по адресу http://mef.codeplex.com/, версия на момент написания поста – Preview 5. На базе MEF построена Visual Studio 2010.

MEF по функциональности похожа на IoC-контейнеры, но авторы не стремились повторить функциональность существующих контейнеров. В MEF есть несколько уникальных фич, которых нету в  IoC-контейнерах.

Сразу примеры, снова поиск фильмов. Возьмем основной код из поста про Unity.

// Фильм
public class Movie
{
    public string Title { get; set; }
    public string Director { get; set; }
    public int Year { get; set; }
}


                
            


Введение в IoC

(Пост на RSDN)

Есть класс A и зависит от от класса B (использует его).

Например так:
public class A
{
   B b = new B();
   void foo()
   { 
      //используем b
   }
}


                
            


Паттерн MVVM. Часть 1.

MVVM – Model – View – ViewModel – паттерн организации PL (presentation layer – уровень представления).

Паттерн MVVM применяется при создании приложений с помощью WPF и Silverlight.   Этот паттерн был придуман архитектором этих самых WPF и Silverlight - John Gossman (его блог). Паттерн MVVM применяется в Expression Blend.

Идеологически MVVM похож на Presentation Model описанный небезызвестным Фаулером, но MVVM сильно опирается на возможности WPF.

Основная особенность MVVM заключается в том, что все поведение выносится из представления (view) в  модель представления (view model).  Связывание представления и модели представления осуществляется декларативными байндингами в XAML разметке. Это позволяет тестировать все детали интерфейса не используя сложных инструментальных средств.

Я сначала хотел кратко описать применение MVVM и Unity для построения PL, но понял что одного поста для описания возможностей MVVM очень мало.

В WPF для передачи данных между объектами и визуальными элементами используются байндинги (binding – привязка) в простонародии биндинги. Передача может быть как однонаправленная, так и двунаправленная. Работают байндинги с помощью зависимых свойств (DependencyProperty) или интерфейса INotifyPropertyChanged. Передача управляющих воздействий от визуальных элементов осуществляется с помощью команд, реализующих интерфейс ICommand.

Для начала надоевший уже пример SayHello.

Как всегда используется супер-сложный класс бизнес логики:

public interface ISayHelloService
{
    string SayHello(string name);
}
 
public class SayHelloSerivce : ISayHelloService
{
    public string SayHello(string name)
    {
        return "Привет, " + name;
    }
}


Паттерн MVP и Unity

MVP – Model View Presenter – паттерн организации PL (presentation layer – уровень представления).

MVP применяется при создании десктопных интерфейсов. Выделяют три комопнента: есть модель – группа классов, которые отдают данные или получают команды, представление – форма обладающая состоянием и некоторым поведением. Презентер создают для отделения бизнес-логики от деталей GUI-фреймворка. В отличие от MVC в MVP представление определяет презентер, а не наоборот.

MVP обычно строится вокруг существующих GUI-фреймворков. На практике существуют две принципиально различные различные реализации паттерна – Supervising Controller и Passive View.
В первом случае логика помещается в обработчики событий button_click, а сами обработчики помещаются в отдельный класс. Для полной изоляции презентера от деталей представления надо писать достаточно много врапперов\адаптеров.
Во втором случае создается пара интерфейсов для общения между представлением и презентером. При совершении какого-либо действия представление напрямую обращается к презентеру, тот выполняет некоторый код и вызывает установку свойств представления. Passive View способствует максимальному перемещению кода в в презентер, что облегчает тестирование.

Создание MVP в WinForms с помощью Unity.

Создаем новое winforms приложение и грохаем оттуда форму.

Для начала определим служебные интерфейсы.

//Маркерный интерфейс представления
public interface IView
{
}


                
                
                
                
            


Unity + ASP.NET MVC

Недавно на хабре появилась статья о Unity. В одном из комментов предложили связать ASP.NET MVC и Unity.

А вот есть прикольная задачка уже не для начинающих — связать ASP.NET MVC с Unity. Требований в целом три:

— обеспечить связывание инфраструктуры контроллеров MVC с инъектором
— модульность
— уникальность контейнера в разрезе сессии

Я бы расписал все, но блин, не успеваю вообще ничего, кроме работы. А вам в рамках обучающих методик будет полезно, имхо.

хабраюзер acerv

Я напишу серию постов о применении Unity в паттернах уровня представления.

Сначала о том что такое MVC.

MVC – Model View Controller – паттерн организации PL (presentation layer – уровень представления). Целью этого паттерна, как и многих других, служит отделение модели (логики работы программы) от представления (средств отображения информации). В итоге применение такого паттерна должно приводить к улучшению тестируемости кода.

В современном виде MVC применяется в вебе. Выглядит так: есть модель – группа классов, которые отдают данные или получают команды, есть различные представления этих данных – HTML, сделанные с помощью какого-либо шаблонизатора, JSON, SOAP, XML или еще какой – либо. Для того чтобы передавать данные от модели к представления вводят контроллер.

Все MVC фреймворки проектируются так, чтобы управление приходило сразу на контроллер. Контроллер вызывает методы модели, если нужно формирует данные для передачи представлению (эти данные называют Presentation Entity, но термин неустоявшийся), и выбирает представление для этих данных.

ASP.NET MVC – фреймворк для ASP.NET, реализующий паттерн MVC.
Контроллеры в ASP.NET MVC – классы, унаследованные от класса Controller, содержащие несколько методов - “действий”  (actions). По умолчанию действия отображаются на различные urlы см помощью механизма раутинга (System.Web.Routing).
Каждое действие возвращает ActionResult, который может вызывать генерацию HTML, сериализацию данных в JSON итд. Можно писать свои ActionResult.

Применение Unity в ASP.NET MVC.

Инфраструктура ASP.NET MVC получает имя контроллера из параметров запроса, формируемых механизмом раутинга, потом вызывает класс ControllerFactory, который по имени возвращает экземпляр контроллера.

Сначала создадим новое asp.net mvc приложение. Оно уже включает в себя большой функционал, его трогать не будем.

Чтобы подключить Unity к механизму создания контроллеров надо написать свой класс ControllerFactory.  По умолчанию используется DefaultControllerFactory, нам надо изменить в нем один метод, который создает объект контроллера.

public class UnityControllerFactory: DefaultControllerFactory
{
    IUnityContainer _container;


                
            


Плачь по SOA

Наверное все слышали о SOA, но у большинства эти три буквы ассоциируются исключительно с ws-* вебсервисами. Хотя на самом деле это очень хорошая концепция построения программной системы из компонент, абсолютно не привязанная к способам реализации взаимодействия.

Принцип SOA состоит в следующем. Вся система разбивается на слабо связные компоненты – сервисы. Сервисы знают друг о друге только контракт. Сервисы в SOA, в отличие от объектов в ООП, не обладают свойством идентичности. Так как сервисы не знают ничего друг о друге, то должен быть общеизвестный сервис, который по контракту может выдать ссылку на сервис.

Так как сервисы не обладают идентичностью, а все ссылки на сервисы с одним контрактом равнозначны, то это позволяет динамически изменять систему, подменять один компоненты другими и достаточно просто балансировать нагрузку.

В мире ws-* вебсервисов контракты реализуются с помощью WSDL, а общеизвестными сервисами являются брокеры, которые по WSDL могут возвращать ссылки на сервисы. Сейчас наблюдается практически полное отсутствие ws-брокеров на просторах интернета. WSDL не описывает семантику, а только синтаксис, поэтому просто получить ссылку на сервис не получится, неизвестно вообще говоря что этот сервис делает. Кроме того нету стандартных контрактов, каждый вендор делает такой контракт как захочется. Это все приводит к тому что идея брокеров на практике оказывается нежизнеспособной. В большинстве библиотек вебсервисов не реализована поддержка UDDI – протокола общения с брокерами. Глобального коммунизма не будет.

А вот в отдельно взятой стране программе такой коммунизм построить можно. Роль контрактов будут играть интерфейсы языка, роль сервисов – объекты, реализующие интерфейсы, а в качестве брокера будет выступать IoC-контейнер. Он как раз умеет по интерфейсу отдавать ссылку на сервис, а его возможность собирать зависимости позволит избавить сами сервисы от общения с брокером.

Кроме того все сервисы (их семантика) находится под контролем. Вполне можно написать тесты, которые проверяют что все сервисы, реализующие заданный контракт, делают именно  то, что от них требуется.



Фабрики объектов в Unity

В этом посте я описал как с помощью LifetimeManager можно научить Unity использовать фабрику для создания объектов.

На самом деле так делать не стоит. В составе Unity есть сборка Microsoft.Practices.Unity.StaticFactory.dll в которой находится расширение контейнера для использования фабрики объектов.

Регистрация фабрики происходит методом RegisterFactory, который принимает делегат FactoryDelegate. Этот делегат принимает параметром IUnityContainer, и возвращает object.

Пример

var r = new Random();
var container = new UnityContainer();
container
    .AddNewExtension<StaticFactoryExtension>()
    .Configure<StaticFactoryExtension>()
        .RegisterFactory<int>("random", c => r.Next());


AOP времени исполнения в Unity

Для тех кто не знает – AOP это Aspect-Oriented Programming, Аспектно-ориентированное программирование.

При написании любой программы программист производит функциональную декомпозицию, то есть разбивает большие блоки функциональности на более маленькие. Но всегда существуют так называемые cross-cutting concerns  или сквозная функциональность, которая используется всеми остальными частями программы, которую невозможно выделить  в отдельный модуль\класс\метод,
Чаще всего такой функциональностью является логгирование, разграничение доступа, управление транзакциями.

Концепция AOP заключается в том что сквозная функциональность выделяется в отдельные сущности , называемые аспектами, и декларативно задается использование аспектов  в коде.

AOP для .NET может быть реализован двумя способами: изменение кода при компиляции инструментами типа PostSharp или макросами языка Nemerle, или перехват вызовов на стадии выполнения.

В составе Unity есть сборка Microsoft.Practices.Unity.Interception, которая содержит расширение контейнера Unity для перехвата вызовов объектов собираемых контейнером.

Чтобы перехватывать вызовы надо контейнеру сообщить что перехватывать, как перехватывать, и зачем перехватывать.
Что перехватывать задается политиками (Policy), как перехватывать определяют перехватчики (Interceptor), зачем перехватывать определяют обработчики вызовов (CallHandlers).
Эти три части механизма перехвата не зависят друг от друга.

Перехватчики – это классы, реализующие интерфейс IInterceptor. В библиотеке есть классы InterfaceInterceptor для перехвата методов интерфейса, VirtualMethodInterceptor – для перехвата виртуальных методов класса, TransparentProxyInterceptor – для перехвата с помощью прокси-классов, используемых для .NET Remoting.

Обработчики вызовов – это классы, которые реализуют интерфейс ICallHandler, в котором только один нужный метод – Invoke.

Политики бывают двух видов – управляемая атрибутами (AttributeDrivenPolicy) и управляемая правилами (RuleDrivenPolicy).
По-умолчанию используется AttributeDrivenPolicy, которая заключается в том что обработчики вызовов задаются атрибутами, унаследованными от HandlerAttribute, и перехватываются только те методы, для которых заданы эти атрибуты (или атрибуты заданы для классов).
RuleDrivenPolicy позволяет задавать какие методы будут перехватываться с помощью набора правил (IMatchingRule)  и какие обработчики будут при этом вызываться.

Подробнее по этой ссылке http://msdn.microsoft.com/en-us/library/dd140045.aspx

Если вы сами пишете код, для которого нужен перехват, то атрибутов и стандартной политики вам будет достаточно. Если вы не можете менять код классов, но хотите их перехватывать, то это можно сделать с помощью задания политик, основанных на правилах заданных в коде или конфигурационном файле.

Пример

Сначала создадим обработчик, который просто выводит Hello, world на консоль

public sealed class HelloWorldAttribute : HandlerAttribute
{
    public override ICallHandler CreateHandler(IUnityContainer container)
    {
        return new HelloWorldCallHandler();
    }
}


                
            


Что делать если Unity-контейнер надо передавать как зависимость в другие компоненты?

Правильный ответ – ничего.

var container = new UnityContainer();
var resolvedContainer = container.Resolve<IUnityContainer>();


Конфигурация Unity

Существует четыре различных способа конфигурирования контейнера.

1)Использование соглашений. Фактически отсутствие явного конфигурирования. В Unity используется очень простое соглашение, что в классе должен быть один конструктор, который принимает все зависимости параметрами.
Такой способ подходит для 90% случаев если вы пишите код сами.

2)Указание зависимостей с помощью атрибутов. Для свойств есть DependencyAttribute, для конструктора указывается InjectionConstructorAttribute, для метода InjectionMethodAttribute. Для параметров конструктора и injection-методов также можно указывать DependencyAttribute.
При навешивании DependencyAttribute на свойство или параметр можно указать имя зависимости.

3)Задание конфигурации в коде при добавлении элемента в контейнер.
Последним параметром метода RegisterType является массив InjectionMember. В этом массиве можно передать объекты типа InjectionProperty, InjectionConstructor и InjectionMethod чтобы указать с помощью каких членов класса проводить инъекцию.
При указании InjectionConstructor и InjectionMethod для каждого параметра можно указать конкретное значение или попросить контейнер резолвить нужный параметр.
Такой способ дает гораздо более многословен, чем все остальные способы, но дает гораздо большую гибкость. С помощью конфигурации в коде можно использовать IoC с классами, к исходникам которых нет доступа.

4)Задание конфигурации в XML.  Этот способ имеет смысл применять только если нужно изменять конфигурацию без  перекомпиляции  программы. Конфигурация в XML не позволяет описывать произвольные типы, поэтому обладает меньшей мощностью по сравнению с конфигурацией в коде.

Подробнее о возможностях конфигурирования Unity можно почитать по ссылкам
http://msdn.microsoft.com/en-us/library/dd203225.aspx
http://msdn.microsoft.com/en-us/library/dd203208.aspx
http://msdn.microsoft.com/en-us/library/dd203195.aspx
http://msdn.microsoft.com/en-us/library/dd203156.aspx
http://msdn.microsoft.com/en-us/library/dd203230.aspx

Overconfiguration или Темная сторона силы

Часто программисты, познакомившись с IoC-контейнерами и оценив их возможности декларативного конфигурирования, начинают использовать конфигурацию по любому поводу. Это приводит к тому что конфиги становятся монструозных размеров и абсолютно нечитаемые. При этом по коду программы абсолютно невозможно понять какой код когда выполняется. Любые изменения затрагивают не только код, но и конфигурацию, что может привести к проблемам при развертывании.

Поэтому при использовании любой технологии, которая может очень сильно конфигурироваться, предпочитайте пользоваться соглашениями (Convetions over Configuration) и размещайте конфигурацию как можно ближе к месту использования. Для этого используйте атрибуты и помещайте код конфигурирования в ту же сборку, где находятся конфигурируемые классы.



Управление временем жизни объектов в Unity

При регистрации класса или объекта в контейнере можно указать объект LifetimeManager, который будет управлять временем жизни экземпляров в контейнере.

Класс LifetimeManager очень простой, в нем всего три метода GetValue, SetValue и RemoveValue, причем последний не используется.
При помещении объекта в контейнер вызывается метод SetValue, а при необходимости получить объект вызывается GetValue и если он вернул null, то создается новый объект.

В библиотеку Microsoft.Practices.Unity входит несколько менеджеров.

TransientLifetimeManager – ничего не сохраняет, GetValue всегда возвращает null, поэтому объект создается каждый раз. Этот менеджер используется по-умолчанию при вызове RegisterType.

ContainerControlledLifetimeManager – сохраняет объект в локальной переменной. Поэтому объект живет столько же, сколько и контейнер. Этот (вернее другой, но с таким же поведением) менеджер используется по-умолчанию при вызове RegisterInstance.

ExternallyControlledLifetimeManager – сохраняет слабую ссылку (WeakReference) на объект. При использовании этого менеджера и вызове RegisterInstance сам вызывающий код должен управлять временем жизни объекта, помещенного в контейнер. Когда используется RegisterType этот менеджер будет выдавать уже существующий экземпляр объекта если он есть.

PerThreadLifetimeManager – сохраняет объекты в ThreadStatic словаре. Таким образом каждый поток в программе будет использовать свой набор объектов.

Для применения Unity в ASP.NET приложениях очень легко реализовать LifetimeManager, который сохраняет объект в контексте или в сессии.

Другие области применения LifetimeManager

В Unity нет возможности регистрации метода создания объектов, но это очень легко исправить с помощью своего LifetimeManager и пары extension-методов.

public class FactoryLifetimeManager<T>: LifetimeManager
{
    Func<T> _factoryMethod;
    LifetimeManager _baseManager;


                
            


Инъекция массивов в Unity

Unity умеет резолвить зависимости-массивы. Для такой зависимости контейнер возвращает объекты всех подходящих типов. Даже при разрешении зависимостей с указанием имени будут возвращены все подходящие типы.

Также этого Unity поддерживает разрешение обобщенных массивов.

Пример

Интерфейс и классы логгеров

public interface ILogger
{
    void Write(string message);
}


                
            


Использование generic-ов в Unity

Unity поддерживает работу с generic-типами. 
Можно в контейнер поместить обобщенный тип,  а потом запросить тип с конкретными параметрами.  Подробнее описано по ссылке http://msdn.microsoft.com/en-us/library/dd203156.aspx

Пример кода, использующего такую фичу.

Рассмотрим обобщенный класс репозитария для работы с данными

public interface IRepository<T>
{
    IQueryable<T> Items();
    void Save(T item);
    void Delete(T item);
}


Рефакторинг legacy кода для использования Unity

Представим себе код, который был написан без применения принципов IoC.
Например такой

public class AccountManager
{
    public void CreateAccount(string userName, string password)
    {
        var accountStore = new AccountStore();
        accountStore.AddNewAccount(userName, password);
    }


                
            


Используем IoC-контейнер Unity

Unity – IoC-контейнер от Microsoft, разработанный группой Patterns&Practicies. Найти его можно по адресу http://www.codeplex.com/unity. Также Unity включен в состав Enterprise Library.

Будет использоваться версия 1.2, последняя на данный момент.

Итак приступим.
Как завещал дядька Фаулер будем рассматривать использования контейнера на примере программы, работающей  с базой данных фильмов.

Чтобы использовать Unity в своей программе надо подключить сборки Microsoft.Practices.ObjectBuilder2 и Microsoft.Practices.Unity.

Нам надо написать класс, который позволяет искать фильмы по различным параметрам.

Предположим что фильмы описываются классом Movie

public class Movie
{
    public string Title { get; set; }
    public string Director { get; set; }
    public int Year { get; set; }
}


Основная задача IoC-контейнеров

Многократно встречал мнение что использовать IoC-контейнер стоит только тогда, когда нужно изменение конфигурации без перекомпиляции программы.
Такое мнение пришло из мира Java, где частью любой крупной программы является множество конфигурационных XML файлов. В других платформах зачастую возможность внешнего конфигурирования используемых компонент избыточна.

На самом деле основной задачей IoC-контейнеров является декларативное управление временем жизни компонент, и зависимостями между ними.

Внешние конфиги, позволяющие менять состав компонент без перекомпиляции, AOP времени исполнения и другие приблуды являются необязательными.



Введение в IoC

(Пост на RSDN)

Есть класс A и зависит от от класса B (использует его).

Например так:
public class A
{
   B b = new B();
   void foo()
   { 
      //используем b
   }
}