Главная страница

Внедрение зависимостей в. Внедрение зависимостей в .NET. Руководство по применению этого механизма. Net приложениях. Книга демонстрирует основные паттерны на обычном языке C#, поэтому вы в полной мере поймете, как работает механизм внедрения зависимостей


Скачать 5.66 Mb.
НазваниеРуководство по применению этого механизма. Net приложениях. Книга демонстрирует основные паттерны на обычном языке C#, поэтому вы в полной мере поймете, как работает механизм внедрения зависимостей
АнкорВнедрение зависимостей в .net
Дата14.12.2019
Размер5.66 Mb.
Формат файлаpdf
Имя файлаВнедрение зависимостей в .NET.pdf
ТипРуководство
#100226
страница40 из 43
1   ...   35   36   37   38   39   40   41   42   43
компонент по умолчанию. Все экспортируемые компоненты равны между собой.
Это и есть первый намек на такое важное понятие MEF, как мощ ност ь импортируемых и экспортируемых компонентов. Количество экспортируемых компонентов должно совпадать с количеством импортируемых компонентов. В таблице 15-2 показано, как MEF сопоставляет импортируемые и экспортируемые компоненты по мощности.
Таблица 15-2: Сопоставление количества атрибутов импорта и экспорта
Export.Single
Export.Many
Import.Single
Равно
Не равно
Import.Many
Равно
Равно
В этом контексте термин "many" используется для обозначения последовательности частей, обычно массива
IEnumerable
. Если мы будем явным образом импортировать множество частей одного и того же контракта, то MEF всегда будет находить соответствие, поскольку нулевое количество атрибутов экспорта есть особый случай множественных атрибутов экспорта.
С другой стороны, явно импортируя один экземпляр, мы получаем несоответствие мощностей в случае наличия нулевого или множественного количества атрибутов экспорта, потому что импорт одного экземпляра означает, что у нас должен быть только один экземпляр.
П римечание
Мощ ность – это один из тех видов размерностей, по которым атрибуты экспорта и импорта должны соответствовать друг другу.
Как вы увидите в разделе 15.2.1, механизм управления жизненным циклом также может играть важную роль в вопросе соответствия частей, но мощность учитывается всегда.
Далее в этом разделе вы увидите, как определять единичные и множественные атрибуты импорта. Но перед тем как приступить к рассмотрению этого вопроса, нам следует

595 рассмотреть процесс экспорта частей в тех ситуациях, когда мы не контролируем классы, участвующие в этом процессе.
Экспорт адапте ров
Самый простой способ экспортировать часть – применить к классу атрибут
[Export]
, но это не всегда возможно сделать. Нам может понадобиться экспортировать классы, которые уже были скомпилированы, а доступа к исходному коду у нас нет. В таких случаях мы не можем применять атрибуты, хотя нам и хотелось бы включить этот класс в компоновку.
И все равно мы можем достичь этой цели, усилив способность MEF экспортировать свойства так же, как и классы. Для примера рассмотрим конструктор
Mayonnaise
: public Mayonnaise(EggYolk eggYolk, OliveOil oil)
Представьте себе, что класс
Mayonnaise и составляющие его зависимости
EggYolk и
OliveOil не подвластны нам. Одним из возможных вариантов в таком случае было бы наследование от первоначального класса и применение атрибута
[Export]
к унаследованному классу:
[Export(typeof(OliveOil))]
[Export(typeof(IIngredient))] public class MefOliveOil : OliveOil { }
Обратите внимание, что если вам необходимо экспортировать и первоначальный конкретный класс, и интерфейс
IIngredient
, то вы должны явным образом установить, что базовый класс (который и является конкретным классом) также должен быть экспортирован. Если бы вы использовали атрибут
[Export]
без указания типа, то вместо базового класса вы бы экспортировали класс
MefOliveOil
Однако если рассматриваемые классы заблокированы, то вы не сможете экспортировать их таким образом. В этом случае, как показано в следующем листинге, можно создать адаптер и экспортировать часть через свойство.
Листинг 15-1: Экспорт
OliveOil через адаптер
1.
public class OliveOilAdapter
2.
{
3.
private readonly OliveOil oil;
4.
public OliveOilAdapter()
5.
{
6.
this.oil = new OliveOil();
7.
}
8.
[Export]
9.
public OliveOil OliveOil
10.
{
11.
get { return this.oil; }
12.
}
13.
}
Строка 8: Экспорт свойства
Класс
OliveOilAdapter
– это совершенно новый класс, который обертывает первоначальный класс
OliveOil и экспортирует его посредством аннотированного

596 свойства. Атрибут
[Export]
можно применять как к свойствам, так и к типам, но в остальном он работает аналогичным образом. Свойство
OliveOil имеет тип
OliveOil
, являющийся, в свою очередь, контрактом, который вы собираетесь экспортировать, поэтому в этом случае вы можете использовать свойство
[Export]
, не задавая явно тип.
П одсказка
Т ип всегда можно экспортировать путем создания адаптера.
Когда у класса, который вам необходимо скомпоновать, есть зависимости, вам нужно экспортировать их посредством адаптера. Как показывает следующий листинг, этот процесс становится более сложным, но пока что вполне управляемым.
Листинг 15-2: Настройка класса, имеющего зависимости
1.
public class MayonnaiseAdapter
2.
{
3.
private readonly Mayonnaise mayo;
4.
[ImportingConstructor]
5.
public MayonnaiseAdapter(
6.
EggYolk yolk, OliveOil oil)
7.
{
8.
if (yolk == null)
9.
{
10.
throw new ArgumentNullException("yolk");
11.
}
12.
if (oil == null)
13.
{
14.
throw new ArgumentNullException("oil");
15.
}
16.
this.mayo = new Mayonnaise(yolk, oil);
17.
}
18.
[Export]
19.
public Mayonnaise Mayonnaise
20.
{
21.
get { return this.mayo; }
22.
}
23.
}
Строка 5-6: Имитатор сигнатуры конструктора
Mayonnaise
Строка 16: Создание
Mayonnaise
Строка 18: Экспорт
Mayonnaise
Чтобы экспортировать класс
Mayonnaise посредством адаптера, вы должны учитывать тот факт, что у него есть зависимости, которые вам нужно будет импортировать. Чтобы иметь возможность создать экземпляр класса, вы должны сымитировать сигнатуру конструктора
Mayonnaise в конструкторе адаптера для того, чтобы вы смогли импортировать все необходимые части. После прохождения соответствующих граничных операторов вы создаете новый экземпляр
Mayonnaise из параметров конструктора и сохраняете результат в приватном поле. Это и есть паттерн Constructor Injection в действии.
Чтобы экспортировать класс
Mayonnaise
, вы можете раскрыть поле mayo как свойство и отметить его атрибутом
[Export]

597
Благодаря
EggYolkAdapter
, похожему на
OliveOilAdapter из листинга 15-1, вы можете создать каталог, состоящий из трех адаптеров, и успешно разрешить экземпляр
Mayonnaise
, даже если вы никогда не изменяли первоначальные классы.
Возможно, вы обратили внимание на атрибут
[ImportingConstructor]
, который появился в листинге 15-2. Это часть другой стороны уравнения. До настоящего момента мы рассматривали процесс экспорта частей. Теперь давайте изучим, как можно импортировать части.
И мпорт частей
В рамках MEF присутствует некоторого рода симметрия. Большинство из тех утверждений, которые мы можем применить к атрибутам экспорта, также применимы к атрибутам импорта. Однако когда дело доходит до паттерна Constructor Injection, нам необходимо прибегнуть к атрибуту
[ImportingConstructor]
, эквивалентов которого для экспортируемых компонентов не существует. Мы видели, как этот атрибут применялся к
MayonnaiseAdapter в листинге 15-2, но он должен применяться всякий раз, когда нам нужно применять паттерн Constructor Injection.
В приведенном примере мы предположили, что класс
Mayonnaise нами не контролируется. Благодаря невероятному стечению обстоятельств мы смогли невзначай перехватить исходный код и теперь можем изменять типы напрямую. В этом случае нам не придется создавать адаптеры, и мы можем применять атрибуты
[Export]
напрямую к классам
Mayonnaise
,
OliveOil и
EggYolk
MEF не распознал паттерн Constructor Injection, поэтому, даже если у
Mayonnaise имеется всего один конструктор, при попытке разрешить этот класс мы сначала получим исключение. Нам необходимо явным образом сообщить MEF о том, какой конструктор он должен использовать при отсутствии конструктора по умолчанию:
[ImportingConstructor] public Mayonnaise(EggYolk eggYolk, OliveOil oil)
[ImportingConstructor]
– это сигнал MEF о том, что тот конструктор, к которому относится этот атрибут, должен использоваться для компоновки типа.
П одсказка
Для конструкторов по умолчанию
[ImportingConstructor]
не нужен. Используйте этот атрибут, если у класса нет конструктора по умолчанию, или если компоновка осуществляется с помощью другого конструктора, а не конструктора по умолчанию.
Кроме того, мы можем использовать атрибут
[Import]
для поддержки паттерна Property
Injection, но к этому вопросу мы вернемся в разделе 15.4.3, который посвящен этому паттерну. Более того, существует атрибут
[ImportMany]
. Который используется для импорта последовательностей частей, но его мы рассмотрим в разделе 15.3.2.
Импорт и экспорт частей основывается на применении атрибутов, а поскольку атрибуты компилируются в типы, это делает MEF негибким. Свою гибкость MEF приобретает благодаря каталогам.

598
Работа с каталогами
Каталог инкапсулирует набор частей, которые контейнер может использовать для компоновки диаграммы объектов. В этом разделе мы приведем обзор различных видов каталогов, доступных в MEF.
Использование каталогов в рамках контейнеров
В разделе 15.1.1 "Разрешение объектов" вы уже видели пример взаимодействия каталога и контейнера: var catalog = new
TypeCatalog(typeof(SauceBéarnaise)); var container = new
CompositionContainer(catalog);
В этом примере используется
TypeCatalog конкретного типа, но вы можете создать
CompositionContainer с любым
ComposablePartCatalog.TypeCatalog
– это всего лишь один из множества дочерних классов. На рисунке 15-4 приведена схема иерархии типов.
Рисунок 15-4: В MEF входит четыре конкретных каталога, но помимо них мы можем определять пользовательские каталоги. Возможно, было бы достаточно просто реализовать каталог, который выступал бы в роли Decorator для других каталогов
(например, каталог фильтрации), между тем, как настоящий пользовательский каталог был бы в него вложен.

599
О пределение
Кат алог – это любой класс, унаследованный от абстрактного класса
ComposablePartCatalog
Как и подразумевает его имя,
ComposablePartCatalog
– это каталог частей, которые
CompositionContainer использует для сопоставления импортируемых и экспортируемых компонентов. Одна из перегрузок конструктора класса
CompositionContainer позволяет передавать
ComposablePartCatalog
, и именно этот конструктор мы использовали до настоящего момента: public CompositionContainer(ComposablePartCatalog catalog, params ExportProvider[] providers)
Помимо того, что этот конструктор принимает в качестве параметра экземпляр
ComposablePartCatalog
, он также принимает и массив param s типа
ExportProviders
, который является еще одним механизмом расширяемости, не рассматриваемым в этой книге.
Поскольку
ComposablePartCatalog
– это абстрактный класс, а
CompositionContainer принимает в качестве параметра любой унаследованный класс, теоретически мы можем создать пользовательские каталоги с самых азов. Это главный
Seam контейнера MEF. Его даже можно использовать для определения других вариантов атрибутивной модели MEF по умолчанию, которая используется для определения импортируемых и экспортируемых компонентов. Несмотря на то, что такой подход возможен, он слишком трудозатратный, поэтому в этой главе мы не будем его рассматривать.
П одсказка
В проекте с открытым исходным кодом MEF Contrib содержится пример пользовательского
ComposablePartCatalog
, полностью замещающего атрибутивную модель конфигурирования более открытой моделью, которая больше похожа на другие
DI-контейнеры.
Все каталоги, входящие в состав MEF в .NET 4, используют атрибуты
[Import]
и
[Export]
для определения импортируемых и экспортируемых данных, но по-разному определяют местоположение частей. Например,
TypeCatalog определяет местоположение частей, считывая атрибуты типов, содержащихся в каталоге.
И спользование каталогов типов
Класс
TypeCatalog позволяет определять каталог, состоящий из перечня типов, на основании предположения о том, что эти типы определяют импортируемые и экспортируемые компоненты с помощью атрибутов. Существует два перегруженных конструктора, которые позволяют нам создавать произвольное количество экземпляров
Type
: public TypeCatalog(params Type[] types) public TypeCatalog(IEnumerable types)

600
В качестве примера для того, чтобы иметь возможность компоновать
Mayonnaise из адаптеров, созданных вами в листингах 15-1 и 15-2, вы можете создать каталог следующим образом: var catalog = new TypeCatalog( typeof(MayonnaiseAdapter), typeof(EggYolkAdapter), typeof(OliveOilAdapter));
Это небольшой каталог, позволяющий разрешать
Mayonnaise
. Если вы удалите любой из трех типов адаптеров, то экспортируемые компоненты будут упущены. Помимо того, что этот каталог позволяет вам разрешать
Mayonnaise
, он также позволяет разрешать
EggYolk и
OliveOil
, но не более того.
Очевидно, что вы могли бы передать в
TypeCatalog большее количество типов, чтобы экспортировать больше данных, но при этом вы должны четко определить список типов.
Это имеет смысл для небольших сценариев с ограниченной областью применения.
Преимущество заключается в том, что вы можете отбирать только те типы, которые вы собираетесь использовать при компоновке. При наличии типов, экспортирующих конкурирующие типы, вы можете отобрать только те типы, которые вам нужны.
П одсказка
Вы можете создать несколько адаптеров, которые совершенно по-разному экспортируют одну и ту же часть, и передать в
TypeCatalog только один из них. При написании шаблонного кода для этой главы я использовал этот прием для того, чтобы варьировать атрибуты
MayonnaiseAdapter
, не редактируя при этом код.
Недостаток применения
TypeCatalog заключается в том, что вы должны явно передавать все типы. При добавлении в сборку нового типа вам также нужно добавить его в
TypeCatalog
, если вы хотите, чтобы он вошел в каталог. Это приводит к нарушению
принципа DRY (Don't Repeat Yourself – Не повторяйся). Вы могли бы решить эту проблему путем написания кода, в котором для просмотра сборки с целью обнаружения всех открытых типов используется рефлексия. Но вам не придется этого делать, поскольку уже существует каталог, выполняющий то же самое.
И спользование каталогов сборок
Класс
AssemblyCatalog предназначен для просмотра сборки с целью поиска всех импортируемых и экспортируемых компонентов, определенных в этой сборке. Этот класс позволяет нам продолжать добавлять части в сборку без необходимости помнить о том, что нужно добавить эту часть еще и в каталог.
Использовать
AssemblyCatalog так же просто, как и создавать экземпляр
Assembly с помощью конструктора: var assembly = typeof(Steak).Assembly; var catalog = new AssemblyCatalog(assembly);
В этом примере вы используете неизбирательный представительский тип (
Steak
) для определения сборки, но подойдет и любой другой метод, который создает соответствующий экземпляр
Assembly

601
Кроме того, существует перегрузка конструктора, которая вместо экземпляра
Assembly принимает в качестве параметра имя файла. Это делает возможными более слабо связанные сценарии, поскольку мы можем заменить .dll файл, не компилируя повторно остальную часть приложения. Это приближает нас к смыслу подключения сценариев расширений в контейнере MEF. Благодаря
AssemblyCatalog мы могли бы написать императивный цикл и создать для каждого найденного нами файла каталог по этому пути.
Однако нам не приходится действовать подобным образом, поскольку в MEF уже есть специально предназначенный для этого каталог.
И спользование каталогов директорий
Основная цель MEF – разрешить использовать сценарии расширений. Универсальная архитектура расширений – определить для расширений особую директорию. Главное приложение будет загружать и использовать любую сборку, размещенную в этой директории.
MEF способствует выполнению такого сценария посредством класса
DirectoryCatalog
. В конструктор мы передаем путь к директории, а он, в свою очередь, ищет в этой директории файлы с расширением .dll и загружает все, что обнаружено в этих сборках: var catalog = new DirectoryCatalog(directory);
Еще одна перегрузка конструктора также позволяет нам задавать шаблон поиска посредством универсальных специальных символов.
П римечание
При использовании MEF в качестве фреймворка расширяемости, что является ключевым его предназначением, предполагается, что
DirectoryCatalog является самым универсальным из используемых каталогов.
Несмотря на то, что в директории мы можем разместить произвольное количество сборок, а затем отбирать их с помощью
DirectoryCatalog
, мы можем захотеть сочетать каталоги из нескольких отличных друг от друга источников. Даже если для включения возможности расширяемости мы используем
DirectoryCatalog
, нам также могут понадобиться некоторые реализации по умолчанию или внутренние реализации соответствующих импортируемых и экспортируемых элементов. Эти реализации не должны размещаться в папке расширений, потому что в этом случае пользователи смогут удалить из приложения жизненно важную функциональность. Возможно, было бы лучше создать такие реализации по умолчанию с помощью
TypeCatalog
, но это означает, что нам пришлось бы соединить разные каталоги в один.
И спользование каталогов агрегатов
Для соединения каталогов можно использовать класс
AggregateCatalog
, который является тем же самым
Composite только с другим именем. Он объединяет в единое целое произвольное количество каталогов и в то же самое время сам является каталогом: var catalog = new AggregateCatalog(catalog1, catalog2); var container = new CompositionContainer(catalog);

602
Входящие в MEF четыре каталога уже обеспечивают достаточную гибкость приложения, но для получения наибольшего контроля мы реализуем также пользовательские каталоги.
Один из примеров, который легко реализовать и использовать, – это каталог фильтрации.
Ре ализация каталога фильтрации
Несмотря на то, что реализация пользовательского каталога с нуля, возможно, является довольно замысловатой затеей, мы можем довольно легко реализовать Decorator, который изменит поведение другого каталога.
Наиболее очевидный пример – каталог фильтрации, который отфильтровывает вложенный каталог. В следующем листинге продемонстрирован пользовательский каталог, который является оберткой для другого каталога и может вступить в игру только с помощью тех составляющих, которые экспортируют контракт, содержащий строку "Sauce". Этот пользовательский каталог можно использовать, чтобы отобрать из каталога всех ингредиентов только соусы.
Листинг 15-3: Реализация пользовательского каталога
1.
public class SauceCatalog : ComposablePartCatalog
2.
{
3.
private readonly ComposablePartCatalog catalog;
4.
public SauceCatalog(ComposablePartCatalog cat)
5.
{
6.
if (cat == null)
7.
{
8.
throw new ArgumentNullException("cat");
9.
}
10.
this.catalog = cat;
11.
}
12.
public override
13.
IQueryable Parts
14.
{
15.
get
16.
{
17.
return this.catalog.Parts.Where(def =>
18.
def.ExportDefinitions.Any(x =>
19.
x.ContractName
20.
.Contains("Sauce")));
21.
}
22.
}
23.
}
Строка 1: Наследование от
ComposablePartCatalog
Строка 3-11: Constructor Injection
Строка 12-22: Реализация фильтра
Чтобы реализовать пользовательский каталог, вы выполняете наследование от абстрактного класса
ComposablePartCatalog
. Поскольку вы пожелали создать обертку для другого каталога, вы запрашиваете этот каталог через Constructor Injection.
Свойство
Parts
– единственный абстрактный член
ComposablePartCatalog
, поэтому это и есть тот единственный член класса, который
1   ...   35   36   37   38   39   40   41   42   43


написать администратору сайта