Внедрение зависимостей в. Внедрение зависимостей в .NET. Руководство по применению этого механизма. Net приложениях. Книга демонстрирует основные паттерны на обычном языке C#, поэтому вы в полной мере поймете, как работает механизм внедрения зависимостей
Скачать 5.66 Mb.
|
необходимо реализовать. При желании вы можете реализовать и другие виртуальные члены, но для этого примера это не 603 потребуется. Фильтр реализуется с помощью выражения Where , которое отфильтровывает все ComposablePartDefinitions , не экспортирующие никакого контракта и содержащие слово "Sauce". SauceCatalog – это конкретный класс, но вы можете обобщить реализацию, чтобы создать универсальный FilteringCatalog . В документации к M EF есть соответствующий пример. П ользовательские каталоги Возможно, листинг 15-3 вас удивил: если нам для создания пользовательского каталога необходимо реализовать только одно единственное свойство, то, как этот процесс может быть сложным? Проблема заключается в том, что ComposablePartDefinition является абстрактным типом и не имеет ни одной открытой реализации. При реализации унаследованного ComposablePartCatalog необходимо также реализовать пользовательский ComposablePartDefinition . Теперь паттерн повторяется, поскольку ComposablePartDefinition определяет еще один абстрактный метод, возвращаемое значение которого относится к типу, не имеющему открытой реализации. Хотя создать пользовательский каталог возможно, но в этой книге этот процесс не рассматривается. Каталоги – существенные строительные блоки MEF. Несмотря на то, что атрибуты статичны, каталоги придают приложению большую гибкость. В MEF встроены четыре каталога, которые содержат элементы, полученные из явных типов, из одной и той же сборки или из найденных в папке сборок. В случае необходимости сочетать части из разных каталогов можно воспользоваться AggregateCatalog MEF поддерживает только возможность конфигурирования частей с помощью атрибутов. Но если нам нужно скомпоновать части, не используя атрибуты, мы всегда можем создать адаптеры, которые импортируют и экспортируют эти части. Т акие адаптеры можно использовать для того, чтобы преодолеть разрыв между статической аттрибутивной моделью и конфигурацией контейнера, которую мы использовали для других DI- контейнеров. Мы можем разделить существующий каталог типов на подклассы с тем, чтобы расфасовать совокупность частей или адаптеров, которые впоследствии можно сочетать с AggregateCatalog для компоновки приложения. До настоящего момента мы рассматривали только то, как можно определить импортируемые и экспортируемые компоненты таким образом, что MEF мог компоновать диаграмму объектов. Существуют и другие стороны механизма внедрения зависимостей, которые мы еще не рассматривали. Один из наиболее важных вопросов – как управлять жизненным циклом объектов. 604 15.2. Управление жизненным циклом В главе 8 обсуждался процесс управления жизненным циклом, в том числе наиболее универсальные стили существования, к примеру, Singleton и Transient. Сделать обзор доступных стилей существования MEF довольно легко, поскольку MEF обладает только двумя стилями существования, продемонстрированными в таблице 15-3. Таблица 15-3: Стили существования MEF Название Комментарии Shared Этот стиль считается стилем по умолчанию, несмотря на то, что он зависит от соответствия импортируемых и экспортируемых элементов. Так в MEF называется стиль существования Singleton. NonShared Т ак в MEF называется стиль существования T ransient . Контейнер отслеживает экземпляры. П римечание В MEF стили существования называются полит иками создания. Реализации в MEF стилей существования T ransient и Singleton эквивалентны стилям существования, описанным в главе 8, поэтому в этой главе я не буду тратить время на их рассмотрение. П одсказка В MEF используемым по умолчанию является стиль Singleton. Этим он отличается от других DI-контейнеров. Как уже обсуждалось в главе 8, Singleton – самый эффективный, но не всегда самый безопасный стиль. Поэтому в M EF эффективность приоритетнее безопасности. В MEF существует только две политики создания, а реализовать пользовательские жизненные циклы в нем невозможно, поэтому по сравнению с остальными главами части 4 этот раздел довольно краток. Вы увидите, как объявлять стили существования для частей и как высвобождать компоненты. После прочтения этого раздела вы сможете создавать политики создания в своем собственном приложении. Сообразно остальному API контейнера MEF политика создания определяется с помощью атрибутов. Объявление политики создания Объявить политику создания можно посредством добавления в класс атрибута [PartCreationPolicy] : [Export(typeof(IIngredient))] [PartCreationPolicy(CreationPolicy.NonShared)] public class SauceBéarnaise : IIngredient { } Для атрибута [PartCreationPolicy] необходимо, чтобы вы указали значение для CreationPolicy . В этом примере в качестве значения CreationPolicy вы указали 605 "NonShared" для того, чтобы объявить SauceBéarnaise как T ransient -компонент. Но как демонстрирует таблица 15-4, у перечисления CreationPolicy есть еще несколько вариантов. Таблица 15-4: Значение CreationPolicy Значение Описание Any Это значение используется по умолчанию. Часть может быть как Singleton- компонентом, так и T ransient . Но пока явно не будет запрашиваться значение NonShared, часть будет вести себя как Shared. Shared Часть является Singleton-компонентом. NonShared Часть всегда выступает в роли T ransient –компонента. П римечание Атрибут [PartCreationPolicy] можно применять только к классам. Он отличается от атрибутов [Import] и [Export] , которые можно применять к классам, членам классов и параметрам. Неудивительно, что мы можем использовать значения Shared и NonShared , но вот то, что мы можем указать значение Any , возможно, слегка вас удивило. Мы вкратце рассмотрим значение Any , но сначала завершим рассмотрение значений Shared и NonShared Экспорт с помощью политик создания Как мы только что обсуждали, политику создания мы задаем с помощью атрибута [PartCreationPolicy] . Если мы не указываем значение этого атрибута, то используется значение Any . Однако если мы поставим этот атрибут рядом с атрибутами [Import] и [ImportingConstructor] , которые вы уже видели в этой главе, то по умолчанию будет использоваться стиль Singleton. В этом контексте два приведенных ниже примера эквивалент ны: [Export(typeof(IIngredient))] public class SauceBéarnaise : IIngredient { } [Export(typeof(IIngredient))] [PartCreationPolicy(CreationPolicy.Shared)] public class SauceBéarnaise : IIngredient { } Единственное отличие между двумя верхними строчками кода и тремя нижними заключается в том, что в нижнем примере вы явно указываете на то, что часть является Singleton-компон ентом. До тех пор, пока импортер не запрашивает конкретную политику создания, поведение приведенных примеров кода будет идентично. Разница состоит в том, что в верхнем примере не указана явная политика создания, т.е. используется значение Any . Несмотря на то, что в большинстве случаев по умолчанию используется поведение, соответствующее стилю Singleton, все намного сложнее. 606 И мпорт с помощью требований политики создания CreationPolicy.Any явно указывает на то, что политика создания части не определена, и что жизненный цикл будет определен посредством сопоставления атрибутов экспорта и импорта. Среди многочисленных вариантов импорта зависимостей в M EF есть возможность, позволяющая нам потребовать, чтобы часть имела конкретную политику создания. Это могло бы выглядеть следующим образом: [ImportingConstructor] public Mayonnaise( [Import(RequiredCreationPolicy = CreationPolicy.NonShared)] EggYolk eggYolk, OliveOil oil) { } Этот конструктор Mayonnaise явно указывает на то, что принимаются только свежие желтки. С точки зрения кулинарии, возможно, это и не кажется таким уж сложным, но, когда дело касается кода, это требование задает строгое ограничение для импортируемой части. Это требование компилируется в класс Mayonnaise посредством атрибута [Import] , которым отмечается аргумент конструктора eggYolk . Обратите внимание на то, что этим атрибутом отмечен только атрибут eggYolk, что позволяет вам для создания нескольких порций майонеза брать оливковое масло из той же самой бутылки. П редупреждение Задание требования политики создания в компилируемом атрибуте потребителя – это разновидность анти-паттерна Control Freak. MEF позволяет это сделать, но вам необходимо воздержаься от такого поведения, поскольку это ограничивает ваши возможности компоновки. Свойство RequiredCreationPolicy потенциально может изменить контекст сопоставления импортируемых и экспортируемых компонентов. Если мы не используем это свойство, то принимается любое значение ( Shared и NonShared ), но при использовании свойства RequiredCreationPolicy несовместимые экспортируемые элементы будут отбрасываться. Помните ли вы таблицу 15-2, в которой описывается, как сопоставляются импортируемые и экспортируемые компоненты по мощности? Сопоставление по политике создания – это еще одна сторона алгоритма сопоставления, используемого в MEF. В таблице 15-5 демонстрируется, как сопоставляются политики создания. Таблица 15-5: Сопоставление импортируемых и экспортируемых компонентов по политике создания Export.Any Export .Shared Export .NonShared Im port.Any Shared Shared NonShared Im port.Shared Shared Shared Не сопоставимы Im port.NonShared NonShared Не сопоставимы NonShared 607 Не забывайте, что сопоставление по политике создания – это всего лишь одна из сторон сопоставления импортируемых и экспортируемых компонентов, и перед тем, как будет выполнено сопоставление, должны быть проверены все ограничения. Не указывать для импортируемых элементов политику создания – наиболее предпочтительный вариант. В этом разделе вы увидели, что политика создания определяется с помощью атрибутов, в общем случае подобных атрибутам импорта и экспорта. Указание политик создания – первая составляющая процесса управления жизненным циклом. Но после разрешения диаграмм объектов, которые могут содержать части, имеющие смешанные жизненные циклы, мы должны не забыть их высвободить. В ысвобож дение объектов Как мы уже обсуждали в разделе 8.2.2 "Управление устраняемыми зависимостями", важно высвободить объекты после того, как мы завершили работу с ними, чтобы можно было уничтожить любые устраняемые экземпляр ы при завершении их жизненного цикла. В MEF выполнить это довольно легко. Мы можем явным образом высвободить экспортируемые элементы или же устранить весь контейнер, если он нам больше не нужен. Высвобожде ние экспортируе мы х компонентов Высвобождать экспортируемые компоненты очень легко, но особенностью MEF является то, что, несмотря на возможность высвобождат ь экспортируемые компоненты, мы не можем высвобожlать экспортированные значения. В чем отличие? Ниже приведено экспортированное значение: var ingredient = container.GetExportedValue Как вы уже видели в разделе 15.1.1 "Разрешение объектов", метод GetExportedValue возвращает экземпляр запрашиваемого типа, таким образом, ingredient является экземпляром IIngredient . Вместо того чтобы запрашивать экспортированное значение, можно запросить экспортируемый компонент: var x = container.GetExport Метод GetExport возвращает Lazy , а не экземпляр IIngredient . Однако вы можете получить экспортированное значение из свойства Value экспортируемого компонента: var ingredient = x.Value; Поскольку x – это экземпляр Lazy , ingredient является экземпляром IIngredient . Если вы собираетесь высвобождать разрешенные компоненты, вы должны использовать экспортируемые компоненты, поскольку для CompositionContainer возможен только один метод Release : public void ReleaseExport Для метода ReleaseExport нужен экспортируемый компонент и неэкспортированное значение. Это означает, что вы не можете напрямую высвобождать экспортированное 608 значение посредством передачи переменной ingredient , а для того, чтобы высвободить его, должны использовать экспортируемый компонент: container.ReleaseExport(x); Поскольку ingredient был создан из x , он высвобождается, когда высвобождается подобный экспортируемый компонент. Устраняемые зависимости должным образом уничтожаются, если заканчивается их жизненный цикл. Высвобождать части столь же просто, как и вызывать метод ReleaseExport . Но для того чтобы иметь возможность вызывать этот метод, вы должны использовать первоначальный экспортируемый компонент. В сценариях, когда один и тот же контейнер разрешает множество экземпляров, важно иметь возможность высвобождать экспортируемые компоненты, не устраняя при этом сам контейнер. Т акой сценарий обычно используется в веб-приложениях и веб-сервисах, в которых один и тот же контейнер управляет сразу несколькими запросами. С другой стороны, в клиентских приложениях необходимо разрешать только одну диаграмму зависимостей, при этом мы можем уничтожить контейнер после закрытия приложения. Высвобожде ние конте йнера Т акие клиентские приложения, как WPF, W indows Forms или консольные приложения, должны руководствоваться простым паттерном Register Release Resolve, создавая только одну диаграмму объектов для всего жизненного цикла приложения. Это означает, что нам нужно всего лишь высвободить диаграмму объектов после окончания жизненного цикла приложения. Несмотря на то, что мы можем высвободить экспортируемый компонент с помощью метода ReleaseExport , проще устранить сам контейнер, и при этом нам не придется хранить ссылку на экспортируемый компонент. После закрытия приложения контейнер уже не нужен, поэтому мы можем соответствующим образом высвободить все части посредством уничтожения самого контейнера: container.Dispose(); При устранении контейнера высвобождаются все части, при этом устраняются как Singleton-, так и Transient-компоненты. На этом наше путешествие по процессу управления жизненным циклом в MEF подходит к концу. Части можно компоновать из компонентов со смешанными стилями существования, и это справедливо даже тогда, когда мы определяем несколько экспортируемых компонентов одной и той же абстракции. Мы еще не рассматривали процесс работы с составными частями, поэтому давайте обратим наше внимание на эту тему. 609 15.3. Работа с составными компонентами DI-контейнеры процветают благодаря их индивидуа льности, но их неопределенность порождает ряд трудностей. При использовании Constructor Injection единичный конструктор предпочтительнее перегружаемых конструкторов, поскольку в этом случае ясно, какой конструктор использовать в ситуации, когда у вас нет выбора. То же самое касается и преобразования абстракций к конкретным типам. Если мы пытаемся преобразовать конкретные составные типы к одной и той же абстракции, это приводит к неопределенности. Несмотря на столь нежелательну ю особенность как неопределенность, нам часто приходится работать с составными реализациями единичного интерфейса. Это может происходить в следующих ситуациях: Для разных потребителей должны использоваться разные специфичные типы Зависимости являются последовательнос тями Используются Decorator'ы В этом разделе мы рассмотрим каждую из этих ситуаций и увидим, как MEF поочереди справляется с каждой из них. После прочтения раздела вы должны будете уметь добавлять атрибуты и успешно высвобождать части даже в тех ситуациях, когда в игру вступают составные реализации одной и той же абстракции. П римечание В этом разделе мы не обсуждаем процесс перехвата во время выполнения, поскольку MEF не поддерживает этот механизм. Иногда нам необходим более тщательный контроль, нежели тот, который предоставляет механизм автоматической интеграции. В следующем разделе описывается, как можно обеспечить более тщательный контроль при работе с MEF. В ыбор среди составных кандидатов Автоматическая интеграция – удобный и мощный инструмент, но предоставляет нам меньшие возможности контроля. Пока все абстракции преобразуются в конкретные типы отдельно друг от друга, никаких трудностей не возникает, но как только мы вводим большее количество реализаций для одного и того же интерфейса, возникает неопределенность. Для начала мы должны повторить то, как MEF справляется с составными экспортируемыми компонентами одной и той же абстракции. Работа с составны ми реализациями одного и того же экспортируе мого компонента Как вы уже видели в разделе 15.1.2 "Определение экспортируемых и импортируемых элементов", можно создать составные части одного и того же экспорта: [ Export( typ eof(IIn gre dient)) ] p ublic c las s Sauce Béa rnaise : I Ingredi ent { } [ Export( typ eof(IIn gre dient)) ] p ublic c las s Steak : IIngred ien t { } 610 В этом примере и SauceBéarnaise , и Steak определяются как экспортируемые компоненты типа IIngredient . Однако в отличие от большинства остальных DI- контейнеров в MEF не используется понятие компонента по умолчанию. Существует либо единственный экспортируемый компонент части, либо несколько компонентов. Это и есть та мощность, которая проиллюстрирована в таблице 15-2. Если вы экспортируете и SauceBéarnaise , и Steak как IIngredient , то получаете составные экспортируемые компоненты, а разрешить их вы можете только посредством импорта составных экземпляров. При наличии двух экспортируемых компонентов попытка разрешить один экземпляр IIngredient приводит к исключению: var ingredient = container.GetExportedValue В этом случае выдается исключение, поскольку существуют составные экспортируемые компоненты IIngredient , и MEF отказывается отбирать только один из них. Это имеет значение при рассмотрении ключевого сценария MEF: в сценариях расширяемости обычно мы будем получать экспортируемые компоненты из сборок, находящихся в соотвествующей папке. Экспортируемые компоненты являются расширениями, поэтому во время проектирования мы еще не знаем, какие экспортируемые компоненты будут нам доступны, если они вообще будут нам доступны. В таком контексте нет смысла отбирать только один экспортируемый компонент, теряя при этом остальные. Мы должны либо сделать экспортируемые компоненты неопределенными каким-то другим способом, либо должны уметь справляться с множественност ью. Далее мы снова ненадолго вернемся к вопросу импорта составных компонентов, но сначала рассмотрим варианты наделения экспортируемых компонентов наибольшей индивидуальност ью. Один из способов сделать экспортируемый компонент более индивидуальным – присвоить ему имя. Перегрузка конструктора атрибута [Export] позволяет присвоить экспортируемому компоненту имя: [Export("sauce", typeof(IIngredient))] public class SauceBéarnaise : IIngredient { } [Export("meat", typeof(IIngredient))] public class Steak : IIngredient { } В этом примере вместо двух частей, порождающих один и тот же экспортируемый компонент ( IIngredient ), определяются два разных экспортируемых компонента: один из которых экспортирует сочетание IIngredient и имени "sauce", а другой – сочетание IIngredient и имени "meat". Теперь нет ни одного неименованного контракта IIngredient П римечание При экспорте только именованных типов отсутствуют экспортируемые компоненты неименованного типа. При попытке разрешить неименованный IIngredient будет выдаваться исключение: var ingredient = container.GetExportedValue |