Pull to refresh

Первые шаги с Unity: DI/IoC & AOP

Reading time7 min
Views70K

Введение


Если Вы когда-нибудь слышали такие слова, как IoC, DI, AoP, но не имеете четкого понимания этих терминов, надеюсь, эта статья поможет в них разораться на примере работы с Microsoft Unity контейнером.

Итак, приступим к теории:

Inversion of Control (IoC) и Dependency Injection (DI)


За определением IoC обратимся к википедии:

Обращение контроля* (Inversion of Control, IoC) — важный принцип объектно-ориентированного программирования, который используется для уменьшения связности в компьютерных программах.

IoC также известен как Dependency Injection Principle. Приём Dependency Injection используется почти во всех framework'ах. Он применяется программистами использующими такие объектно-ориентированные языки программирования как Smalltalk, C++, Java или языки платформы .NET.


*Сразу же хотелось оговориться, что мне больше по душе не употреблять для IoC русскоязычного перевода, но всё же если меня заставят это сделать, что я употреблю его, как "инверсия управления"

За определением DI так же обратимся к википедии:

Внедрение зависимости (англ. Dependency injection) обозначает процесс предоставления внешней зависимости программному компоненту и является специфичной формой «обращения контроля (англ. Inversion of control)», где изменение порядка связи является путём получения необходимой зависимости.


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

Для разрешения проблем зависимостей одного объекта от другого обычно на объект, имеющий зависимость возлагают ответственность за создание/получение необходимого ему объекта (Хотелось бы к слову обратить Ваше внимание на такие шаблоны, как Factory, Registry).

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

Думаю, тут будет полезно привести список .NET DI/IoC контейнеров:



Aspect-oriented programming (AoP)



Признаю, я дотошный почитатель википедии:

Аспектно-ориентированное программирование (АОП) — парадигма программирования, основанная на идее разделения функциональности, особенно сквозной функциональности, для улучшения разбиения программы на модули.


Хотелось бы добавить, что Аспектно-ориентированное программирование (АОП) так же призвано прийти на помощь ООП для того, чтобы сократить количество написанного в приложениях кода.

Чаще всего использование АОП сводится к осуществлению повседневных рутинных задач, таких как логгирование,  кэширование, валидация, так же управление доступом. Можно возразить и сказать, что можно решить эту проблему можно при помощи обволакивания ваших бизнес-объектов шаблона Decorator (рус.), но дело в том, что при этом возникает необходимость изменения кода потребителя, а АОП позволяет сделать это легко и непринужденно.

Известные framewоrk’и:



О Microsoft Enterprise Library


Как известно, в октябре 2008 года Microsoft обновили версию Enterprise Library до 4.1, соотвественно одну из её составляющих Unity Application Block до версии 1.2. Разработкой Microsoft Enterprise Library занимается группа patterns & practices. Microsoft Enterprise Library представляет собой набор функциональных блоков, призваных помочь разработчикам в выполнении обычных рутинных задач. Функциональные блоки — что-то типа руководства, предоставляемый в их составе исходный код можно использовать "как есть", расширять или изменять разработчикам в соответствии с их нуждами. Microsoft Enterprise Library состоит из следующих блоков:

  • Caching Application Block. Разработчики могут использовать этот блок для реализации кэширования в своих приложениях. Поддерживает так же подключаемые кэш провайдеры.
  • Cryptography Application Block. Разработчики могут использовать этот блок для реализации хеширования и симметричного шифрования в приложениях.
  • Data Access Application Block. Разработчики могут использовать этот блок для реализации стандартных действий с базой данных в своих приложениях.
  • Exception Handling Application Block. Разработчики и редакторы правил могут использовать этот блок для реализации последовательной стратегии обработки исключений, которые происходят на всех архитектурных слоях корпоративных приложений.
  • Logging Application Block. Разработчики могут использовать этот блок для реализации стандартных функций логирования.
  • Policy Injection Application Block. Разработчики могут использовать этот блок для реализации политик перехвата для того, чтобы насытить приложение такими возможностями, как логирование, кеширование, обработка исключений и валидация на уровне всего приложения.
  • Security Application Block. Разработчики могут использовать этот блок для реализации механизма авторизации и безопасности приложений.
  • Unity Application Block. Разработчики могут использовать этот блок, как легковесный, расширяемый dependency injection контейнер с поддержкой внесения зависимостей в конструктор, свойство и вызов методов, таким же образом, как и реализация перехвата, через расширения.
  • Validation Application Block. Разработчики могут использовать этот блок для реализации правил валидации для бизнес-объетов, используемых в приложении.


О Unity Application Block



легковесный, расширяемый dependency injection контейнер с поддержкой внесения зависимостей в конструктор, свойство и вызов методов, таким же образом, как и реализация перехвата, через расширения. Вы можете использовать его вместе с Enterprise Library (EntLib) для генерации объектов, как ваших собсвенных, так и Enterprise Library. Однако, Unity Application Block отличается от остальных функциональных блоков EntLib в нескольких ключевых моментах:

  • Вы можете использовать Unity Application Block как автономный механизм внесения зависимостей, не нуждаясь в установленной EntLib.
  • Unity Application Block может быть сконфигурирован как с помощью спецализированных файлов конфигурации, так и в run-time режиме с помощью вашего кода.
  • Unity Application Block независим от ядра EntLib, ровно как от системы конфигурации EntLib. Он содержит свой собственный механизм для конфигурирования, хотя, при желании вы можете использовать механизм конфигурации EntLib.


Долгожданный In Action



Требования



Для работы приведенного ниже примера нам понадобятся:

  1. Установленный .NET Framework 3.5 SP1
  2. Ваша любимая IDE (Notepad..Visual Studio 2008 SP1)
  3. Установленный Unity
  4. 15-20 мин времени и немного интереса


Процесс



Для начала, создадим ConsoleApplication под названием UnityInAction1.

Далее добавим ссылки на следующие сборки:

  • Microsoft.Pratices.ObjectBulder2
  • Microsoft.Pratices.Unity
  • Microsoft.Pratices.Unity.Interception.


Создадим в нашем приложении интерфейс ILogger:

  1. namespace UnityInAction1
  2. {
  3.     interface ILogger
  4.     {
  5.         void Write(string msg);
  6.     }
  7. }

* This source code was highlighted with Source Code Highlighter.


Далее имплементируем его в классе Logger:

  1. using System;
  2.  
  3. namespace UnityInAction1
  4. {
  5.     class Logger : ILogger
  6.     {
  7.         #region ILogger Members
  8.  
  9.         public void Write(string msg)
  10.         {
  11.             Console.WriteLine();
  12.             Console.WriteLine("*** In logger ***");
  13.             Console.WriteLine(String.Format("message {0}", msg));
  14.         }
  15.  
  16.         #endregion
  17.     }
  18. }

* This source code was highlighted with Source Code Highlighter.


Так бы выглядело наше обычное приложение:

  1. using System;
  2.  
  3. namespace UnityInAction1
  4. {
  5.     class Program
  6.     {
  7.         static void Main(string[] args)
  8.         {
  9.             ILogger logger = new Logger();
  10.             logger.Write("Моё сообщение");
  11.             Console.ReadKey();
  12.         }
  13.     }
  14. }

* This source code was highlighted with Source Code Highlighter.


Однако, с точки зрения подхода IoC/DI нас это не совсем устраивает, в связи с этим мы хотим поместить наш Logger в контейнер (к примеру для того, чтобы заменить [причём заменить нужно будет только в месте конфигурирования контейнера, а не по всему покрытию кода, как это обычно бывает] его на заглушку при проведении тестирования):

  1. using System;
  2. using Microsoft.Practices.Unity;
  3.  
  4. namespace UnityInAction1
  5. {
  6.     class Program
  7.     {
  8.         static void Main(string[] args)
  9.         {
  10.             IUnityContainer container = new UnityContainer();
  11.             container.RegisterType<ILogger, Logger>(new ContainerControlledLifetimeManager());
  12.  
  13.             var logger = container.Resolve<ILogger>();
  14.             logger.Write("Моё сообщение");
  15.             Console.ReadKey();
  16.         }
  17.     }
  18. }

* This source code was highlighted with Source Code Highlighter.


Отлично, думаю данного примера вполне достаточно для старта.

Теперь добавим требование замера времени выполнения метода ILogger.Write() для всех имплементаций этого интерфейса, понятно, что с помощью ООП реализация данного условия превращается в затруднее, в связи с этим посмотрим на данный пример с точки зрения АОП. Для начала немного переопределим описание ILogger с помощью введения нового атрибута .NET:

  1. namespace UnityInAction1
  2. {
  3.     interface ILogger
  4.     {
  5.         [Stopwatch]
  6.         void Write(string msg);
  7.     }
  8. }

* This source code was highlighted with Source Code Highlighter.


Описание атрибута Stopwatch:

  1. using System;
  2. using Microsoft.Practices.Unity.InterceptionExtension;
  3.  
  4. namespace UnityInAction1
  5. {
  6.     class Stopwatch : HandlerAttribute
  7.     {
  8.         public override ICallHandler CreateHandler(Microsoft.Practices.Unity.IUnityContainer container)
  9.         {
  10.             return new StopwatchCallHandler();
  11.         }
  12.     }
  13.    
  14.     public class StopwatchCallHandler : ICallHandler
  15.     {
  16.         public int Order {get; set;}
  17.  
  18.         public StopwatchCallHandler() : this(0) {}
  19.  
  20.         public StopwatchCallHandler(int order)
  21.         {
  22.             Order = order;
  23.         }
  24.  
  25.         #region ICallHandler Members
  26.  
  27.         public IMethodReturn Invoke(IMethodInvocation input, GetNextHandlerDelegate getNext)
  28.         {
  29.             System.Diagnostics.Stopwatch sw = new System.Diagnostics.Stopwatch();
  30.             sw.Start();
  31.  
  32.             var result = getNext().Invoke(input, getNext);
  33.  
  34.             sw.Stop();
  35.  
  36.             Console.WriteLine();
  37.             Console.WriteLine(String.Format("Прошло времени {0} мс", sw.ElapsedMilliseconds));
  38.  
  39.             return result;
  40.         }
  41.  
  42.         #endregion
  43.     }
  44. }

* This source code was highlighted with Source Code Highlighter.


Ну и добавим расширение для нашего контейнера:

  1. using System;
  2. using Microsoft.Practices.Unity;
  3. using Microsoft.Practices.Unity.InterceptionExtension;
  4.  
  5. namespace UnityInAction1
  6. {
  7.     class Program
  8.     {
  9.         static void Main(string[] args)
  10.         {
  11.             IUnityContainer container = new UnityContainer();
  12.             container.RegisterType<ILogger, Logger>(new ContainerControlledLifetimeManager());
  13.             container.AddNewExtension<Interception>();
  14.             container.Configure<Interception>()
  15.                 .SetInterceptorFor<ILogger>(new TransparentProxyInterceptor());
  16.            
  17.             var logger = container.Resolve<ILogger>();
  18.             logger.Write("Моё сообщение");
  19.             Console.ReadKey();
  20.         }
  21.     }
  22. }

* This source code was highlighted with Source Code Highlighter.


Теперь каждая имплементация ILogger.Write() будет выводить замеры времени выполнения этого метода, разве это не отлично?

Материалы



  • Написание это статьи было инициировано просмотром небольшого скринкаста от David Hayden. Примеры кода с небольшими поправками позаимствованы из него.
  • При написании статьи, в основном, я руководствовался базовыми знаниями, почерпнутыми когда-то из замечательной книги “Applying Domain-Driven Design and Patterns” by Jimmy Nilsson.
  • Так же замечательной кладезью знаний Википедия.


В завершение



Надеюсь в данной статье дано хорошее начало для ваших изысканий в области качественного кода.

P.S. Это мой первый пост на хабре, поэтому немного о себе: горизонт моих интересов очень широк, в технической его составляющей это .NET-разработка (ASP.NET, Sharepoint, WWF, Silverlight), методики разработки (Agile, Scrum, XP), техники разработки (TDD, DDD, MDA, AOP).
Tags:
Hubs:
Total votes 37: ↑33 and ↓4+29
Comments19

Articles