Опубликован: 13.12.2011 | Доступ: свободный | Студентов: 1021 / 34 | Оценка: 4.29 / 4.57 | Длительность: 13:56:00
Лекция 11:

Реализация паттерна MVVM с использованием IoC-контейнера, как метод избавления от зависимости между компонентами системы

< Лекция 10 || Лекция 11: 1234 || Лекция 12 >
Аннотация: В лекции рассматривается реализация MVVM шаблона с применением инверсии зависимостей и аспектно-ориентированного программирования для максимальной инкапсуляции слоев приложения.

Цель лекции: показать читателям на примере фрагментов кода организацию многослойного кроссплатформенного Silverlight/WPF MVVM приложения при помощи IoC контейнера, а также разобрать пример использования Managed Extensibility Framework для разрешения зависимостей между компонентами системы.

Принцип инверсии зависимостей

Инверсия зависимости – это особый вид инверсии контроля, который применяется в Объектно-ориентированном подходе для удаления зависимостей между классами. Зависимости между классами превращаются в ассоциации между объектами. Ассоциации между объектами могут устанавливаться и меняться во время выполнения приложения. Это позволяет сделать модули менее связанными между собой.

Можно выделить 2 основных принципа инверсии зависимостей:

  • Модули верхнего уровня не должны зависеть от модулей нижнего уровня. Оба типа модулей должны зависеть от абстракций;
  • Абстракция не должна зависеть от реализации. Реализация должна зависеть от абстракции.

Рассмотрим пример программы, которая копирует в файл данные, введенные с клавиатуры.

Схема программы копирования данных

Рис. 16.1. Схема программы копирования данных

Здесь используются 3 класса: один класс (иногда его называют сервис) отвечает за чтение с клавиатуры, второй – за вывод в файл, а третий высокоуровневый класс объединяет два низкоуровневых класса с целью организации их работы.

Класс Сopy может выглядеть примерно следующим образом:

  1: public static class CopyManager
  2: {
  3:     public static void Copy()
  4:     {
  5:         var keyboard = new Keyboard();
  6:         var file = new File();
  7:         byte[] buffer;
  8: 
  9:         while ((buffer = keyboard.Read()) != null)
 10:         {
 11:             file.Write(buffer);
 12:         }
 13:     }
 14: }

Низкоуровневые классы Keyboard и File обладают высокой гибкостью. Можно легко использовать их в контексте, отличном от класса CopyManager. Однако сам класс CopyManager не может быть повторно использован в другом контексте. Например, для отправки данных из файла системному обработчику логов.

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

Схема программы с введенными абстракциями

Рис. 16.2. Схема программы с введенными абстракциями
  1: public interface IReader
  2: {
  3:     public byte[] Read();
  4: }
  5:  
  6: public interface IWriter
  7: {
  8:     public void Write(byte[] arg);
  9: }

Класс CopyManager должен полагаться только на выработанные абстракции и не делать никаких предположений по поводу индивидуальных особенностей объектов ввода/вывода:

  1: public static class CopyManager
  2: {
  3:     public static void Copy(IReader reader, IWriter writer)
  4:     {
  5:         byte[] buffer;
  6:
  7:         while ((buffer = reader.Read()) != null)
  8:         {
  9:             writer.Write(buffer);
 10:         }
 11:     }
 12: }

Примерно следующим образом выглядит использование данного класса:

  1: CopyManager.Copy(new Keyboard(), new File());

Теперь класс Copy можно использовать в различных контекстах копирования. Изменение его поведения достигается путем ассоциации его с объектами других классов (но которые зависят от тех же абстракций).

Несмотря на простоту выполненных действий, был получен очень важный результат. Теперь код обладает следующими качествами:

  • класс может быть использован для копирования данных в контексте, отличном от данного;
  • возможно добавлять новые устройства ввода/вывода, не меняя при этом класс Copy.

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

Формы инверсии зависимостей

Существует две формы инверсии зависимостей: активная и пассивная. Различие между ними состоит в том, как объект узнает о своих зависимостях во время выполнения.

При использовании пассивной формы объекты зависимости внедряются в зависимый объект. Зависимому объекту не надо прилагать никаких усилий, все нужные сервисы он получает через свой интерфейс.

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

Каждая из форм инверсии зависимостей имеет подтипы, которые характеризуют детали связывания объектов между собой.

Пассивная инверсия зависимостей (Dependency Injection):

  • внедрение через конструктор: для ассоциирования объекта с конкретными реализациями абстракций используется конструктор. При использовании этого типа инверсии зависимостей необходимые объекты передаются в конструктор в качестве аргументов.
  • внедрение через устанавливаемое свойство: требуется определение отдельного свойства, имеющего set-метод, для каждого из инъецируемых объектов. От предыдущего типа инъекции она отличается местом инъекции. Следует отметить, что внедрение через конструктор и внедрение через устанавливаемое свойство не исключают друг друга.
  • внедрение через интерфейс: задаются интерфейсы, которые определяют методы для связывания, один интерфейс на каждую зависимость. Зависимый объект должен реализовывать все эти интерфейсы. Определяется также единый интерфейс для всех сервисов. Каждый сервис реализует этот интерфейс таким образом, чтобы внедрить себя в зависящий объект. Таким образом, сервисы сами внедряют себя в зависимый объект посредством установленного интерфейса.
  • внедрение через поле: в .NET и Java существует возможность получить доступ к private/protected полям объекта. Эта техника может быть использована для внедрения сервисов в зависящий объект напрямую, без использования set-методов и конструкторов.

Активная инверсия зависимостей (Dependency Lookup):

  • pull-подход: предполагается наличие в системе общедоступного объекта, который знает обо всех используемых сервисах. В качестве такого объекта может выступать объект, реализующий паттерн Service Locator. Локатор реализует паттерн синглетона, благодаря чему доступ к нему можно получить из любого места приложения.
  • push-подход: данная методика отличается от pull-подхода тем, как объект узнает об объекте-локаторе. При использовании pull-подхода класс сам получал локатор посредством класса-синглетона. Push-подход характеризуется тем, что объект-локатор (или как его иногда называют контекст) передается в класс извне (обычно через конструктор).

IoC контейнер

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

Одной из реализаций IoC контейнера является MEF. Это очень мощный проект, который затрагивает очень много аспектов конструирования ПО. Одним из таких аспектов является конструирование объекта на основании его связей. Причем связи между объектами могут задаваться в виде атрибутов и анализироваться в процессе выполнения приложения.

< Лекция 10 || Лекция 11: 1234 || Лекция 12 >