Внедрение зависимостей в. Внедрение зависимостей в .NET. Руководство по применению этого механизма. Net приложениях. Книга демонстрирует основные паттерны на обычном языке C#, поэтому вы в полной мере поймете, как работает механизм внедрения зависимостей
Скачать 5.66 Mb.
|
Chili con Carne. П одсказка Вместо того чтобы экспортировать и импортировать сам тип Spiciness , можно использовать пользовательс ку ю строку в качестве совместно используемого контракта. Для этого потребуется добавить дополнительный атрибут [Import] в аргумент конструктора spiciness с целью определения контракта. С помощью адаптеров и контрактов можно должным образом сопоставить простейшие типы и импортируемые компоненты. Такой подход можно использовать в ситуациях, когда все типы и конструкторы являются открытыми. А что нам делать с типами, у которых нет открытых конструкторов? Компоновка частей без открытых конструкторов Экземпляры некоторых классов нельзя создать с помощью открытого конструктора. Вместо открытых конструкторов для создания экземпляров такого типа нам приходится использовать некоторого рода фабрику. Для DI-контейнеров это всегда проблематично, поскольку по умолчанию им нужны открытые конструкторы. Рассмотрим приведенный ниже пример конструктора открытого класса JunkFood : internal JunkFood(string name) Даже если класс JunkFood является открытым, конструктор расположен внутри него. Очевидно, экземпляры JunkFood должны создаваться с помощью статического класса JunkFoodFactory : public static class JunkFoodFactory { public static IMeal Create(string name) { return new JunkFood(name); } } Предположим, что вы не можете изменить это API. Как вы тогда поступите в этой ситуации, чтобы суметь должным образом сынтегрировать и скомпоновать JunkFood ? Ответ на этот вопрос точно такой же, как и для всех остальных случаев, когда вы не можете изменить первоначальный экспортированный тип: используйте адаптер подобно тому, как это продемонстрировано в следующем листинге. 620 Листинг 15-6: Экспорт типа, имеющего внутренний конструктор public class JunkFoodAdapter { private readonly IMeal junk; public JunkFoodAdapter() { this.junk = JunkFoodFactory.Create("chicken meal"); } [Export] public IMeal JunkFood { get { return this.junk; } } } В JunkFoodAdapter инкапсулировано знание о том, что экземпляр JunkFood создается с помощью метода JunkFoodFactory.Create . Этот метод создает экземпляр в конструкторе и импортирует его через свойство. Поскольку тип свойства – IMeal , то он также является и экспортированным контрактом. С помощью расположенного в каталоге класса JunkFoodAdapter вы можете успешно разрешать IMeal и возвращать экземпляра блюда из курицы JunkFood Последним рассматриваемым нами отклонением от Constructor Injection является Propert y Injection. Интегрирование с помощью Property Injection Property Injection – это менее определенная форма механизма внедрения зависимостей, поскольку компилятор не принудает нас задавать значение свойства, доступного для записи. По иронии MEF был задуман скорее как использующий паттерн Property Injection, а не Constructor Injection. Это объясняет тот факт, что нам не нужно явно применять атрибуты ко всему, что мы собираемся скомпоновать: с точки зрения MEF, паттерн Property Injection (являющийся неопределенным) является используемым по умолчанию, а Constructor Injection – менее идиоматичным вариантом. Несмотря на то, что я считаю эту точку зрения одновременно и устаревшей, и неверной, это все же позволяет без затруднений использовать в MEF паттерн Property Injection. Все, что нам приходится делать, – применять к свойству атрибут [Import] Рассмотрим класс CaesarSalad : public class CaesarSalad : ICourse { public IIngredient Extra { get; set; } } По всеобщему заблуждению в состав салата "Цезарь" входит курица. По своей сути "Цезарь" является салатом, но, поскольку с курицей он вкуснее, то ее часто предлагают использовать в нем в качестве дополнительного ингредиента. Класс CaesarSalad моделирует такую возможность посредством доступного для записи свойства под названием Extra 621 Чтобы разрешить использовать для CaesarSalad паттерн Propert y Injection, вам необходимо просто применить атрибут [Import] : [Import(AllowDefault = true)] public IIngredient Extra { get; set; } В этой книге я последовательно рассматриваю паттерн Property Injection, который применяется в тех случаях, когда есть возможность передавать внешние зависимости. Это имеет смысл, поскольку компилятор не принуждает вас присваивать свойству значение (в отличие от аргумента конструктора). Но MEF не придерживается этой точки зрения. По умолчанию импорт должен выполняться, пока вы явно с помощью свойства AllowDefault не укажете, что делать это необязательно. Чтобы продолжать соответствовать описанному выше паттерну Propert y Injection, свойству AllowDefault вы присваиваете значение true Это означает, что MEF не будет выдавать исключение, если не сможет импортировать IIngredient Вы должны знать, что если свойство AllowDefault имеет значение true , то вместо того, чтобы игнорировать свойство, когда импорт не может быть выполнен, MEF будет явным образом присваивать свойству значение по умолчанию (в этом случае null ). Чтобы применить эту возможность, вы должны быть готовы к работе с null -значениями, но это приведет к разрушению инвариантов класса. Вы должны использовать большие значения, чтобы избежать присваивания null -значений приватным полям. Один из способов работы с null -значениями – молча поглотить такое значение: 1. [Import(AllowDefault = true)] 2. public IIngredient Extra 3. { 4. get { return this.extra; } 5. set 6. { 7. if (value == null) 8. { 9. return; 10. } 11. this.extra = value; 12. } 13. } Строка 7-10: Молчаливое игнорирование null -значений Вы можете явным образом проверить, имеет ли свойство значение null , и выйти, если вызывающий оператор пытается внедрить null -значение. Такое поведение приводит к нарушению принципа "наименьшего удивления" (Principle of Least Surprise), поскольку вызывающих операторов может удивить тот факт, что присваивание значения не дало никакого результата, даже если при этом не выдавалось никакого исключения. И снова вы пришли к тому, что Property Injection – очень проблематичный паттерн, и лучше всего избегать его использования, пока оно не будет оправданно. Известно, что Property Injection характерен для MEF, но, как часто бывает, все зло кроется в деталях. Даже при работе с MEF я предпочитаю использовать Constructor Injection, мой любимый паттерн. 622 В этом разделе вы увидели, как можно использовать MEF для работы с более трудными API разработки. Процесс применения Property Injection, а также всего остального, к чему мы можем обратиться с помощью экспортируемых адаптеров, довольно прост. Такой подход всегда является универсальным решением, если все перестает работать, а мы не можем модифицироват ь части. 623 15.5. Резюме Среди всех остальных DI-контейнеров, охваченных в части 4, MEF является самым особенным. Во-первых, это единственная технология компоновки, официально поставляемая и поддерживаемая компанией Microsoft . Во-вторых, MEF, в действительности, является не DI-контейнером, а фреймворком расширяемости (как видно из его названия Managed Extensibility Framework), поэтому рассматривать его с той позиции, как если бы он был DI-контейнером, было бы не правильно. Между MEF и другими DI-контейнерами столько схожих черт, что эта глава не только является обоснованной, но и необходимой. Вы должны четко понимать, почему M EF не является DI-контейнером, чтобы принять правильное решение о том, когда его можно использовать. Эта глава продемонстрировала, что мы можем выдавить из MEF большую часть функциональности обычного DI-контейнера, но при этом используя достаточно неудобные способы. Наиболее проблемая сторона MEF – зависимость от атрибутов, потому что атрибуты сильно привязывают к типам такие вопросы, как жизненный цикл и отбор импортируемых компонентов из совокупности компонентов. В сценариях расширяемости это не является проблемой, но в процессе компоновки полноценного приложения это ограничение приводит к возникновению трудностей. В ситуациях, когда мы не можем или не хотим помечать типы атрибутами MEF, мы можем создавать адаптеры, которые импортируют и экспортируют соответствующие части ради реальной реализации. Мы можем рассматривать такие адаптеры MEF в качестве конфигурационного API, но в отличие от большинства других строго типизированных свободных интерфейсов DI-контейнеров это API довольно громоздко. Однако адаптер MEF – универсально используемая хитрость, которую можно использовать, чтобы справиться с конкретными проблемами, возникающими в MEF. Можно использовать адаптеры не только для компоновки не атрибутивных типов, но также для экспорта частей из методов фабрики и для много другого. Имеет ли смысл использовать в приложениях MEF в качестве DI-контейнера? Ответ на этот вопрос зависит от обстоятельств. Одним из сильных аргументов в пользу использования MEF является тот факт, что MEF является частью .NET 4 и Silverlight 4, поэтому если для приложения будут использоваться эти платформы, MEF в них уже будет доступен. Дело не только в удобстве применения. Для тех организаций, в которых согласно их внутренней политике должны использоваться только официальные технологии компании Microsoft, использование MEF может стать большим преимуществом. Поскольку MEF – это официальный продукт компании Microsoft , нам предоставляется другой уровень поддержки, нежели тот, который мы получали при использовании других DI-контейнеров. Нам предоставляется такая же поддержка, как и при использовании .NET и Silverlight, при этом мы можем быть уверены, что в MEF не возникнет неполадок. Однако эти преимущества могут не перевесить недостатки использования MEF в тех ролях, для которых она не была сконструирована. MEF был создан для сценариев расширяемости, поэтому имеет смысл использовать его в приложениях, в которых расширяемость является важной чертой. В таких приложениях, возможно, имеет смысл 624 расширить ответственность MEF до композиции всего приложения, поскольку эта возможность уже используется. Для приложений, в которых расширяемость не играет значительной роли, возможно, имеет смысл выбрать специально предназначенный для композиции объектов DI- контейнер. Неважно, какой DI-контейнер вы выбрали, или даже если вы предпочитаете Poor Man's DI, я надеюсь, что эта книга передала вам следующу ю мысль: механизм внедрения зависимостей не основывается на какой-то конкретной технологии, например, на конкретном DI-контейнере. Приложения могут и должны создаваться с помощью DI- дружественных паттернов и приемов, описанных в этой книге. Если мы в этом преуспели, то огромное значение имеет выбор DI-контейнера. DI-контейнер – это инструмент, который компонует наше приложение, но в идеале мы должны уметь заменять один контейнер на другой, не переписывая при этом другие части нашего приложения, кроме как Com position Root. |