Внедрение зависимостей в. Внедрение зависимостей в .NET. Руководство по применению этого механизма. Net приложениях. Книга демонстрирует основные паттерны на обычном языке C#, поэтому вы в полной мере поймете, как работает механизм внедрения зависимостей
Скачать 5.66 Mb.
|
Откуда мне его получить? Перейдите на страницу http://autofac.org и нажмите на соответствующу ю ссылку в списке рекомендуемых загрузок. Из Visual Studio 2010 можно получить его посредством NuGet. Имя пакета – Autofac. Что находится в загруженном файле? Можно загрузить zip-файл, содержащий предварительно скомпилированные бинарные файлы. Кроме того, можно загрузить исходный код и скомпилить его самостоятельно, хотя при этом будет трудно определить, какие изменения к какому релизу относятся. Последняя составляющая номера сборки (например, для используемой в этой главе сборки это 724) соответствует ревизии исходного кода, но для того, чтобы ее определить, понадобится система управления версиями Mercurial. Какие платформы поддерживаются? Поддерживаются версии .NET 3.5 SP1, .NET 4, Silverlight 3, Silverlight 4. Кроме того, доступны и другие версии, поддерживающие .NET 2.0, 3.0 и Silverlight 2 (для этого во вкладке Download (Загрузить) выберите пункт All Releases (Все релизы)). Сколько он стоит? Нисколько. Это программное обеспечение с открытым исходным кодом. Откуда мне получить помощь? Коммерческую поддержку можно получить от компаний, связанных с разработчиками Autofac. Подробную информацию можно получить на сайте http://code.google.com /p/autofac/ wiki/ Comm ercialSupport Невзирая на коммерческую поддержку, Autofac все еще остается программным обеспечением с открытым исходным кодом, которое обладает процветающей экосистемой, поэтому, скорее всего (но не гарантированно), помощь можно получить на официальном форуме http://groups.google.com/gro up/autofac На какой версии Autofac основана эта глава? 2.4.5.724. Процесс использования Autofac слегка отличается от процесса использования других DI- контейнеров. Как показывает рисунок 13-2, это более явный процесс, состоящий из двух шагов: сначала мы конфигурируем ContainerBuilder , а затем с помощью него создаем контейнер, который можно использовать для разрешения компонентов. 496 Рисунок 13-2: При работе с Autofac сначала создается и конфигурируется экземпляр ContainerBuilder . Затем с помощью него создается контейнер, который впоследствии можно использовать для разрешения компонентов. Обратите внимание на то, что последовательность действий больше похожа на работу с контейнерами Castle W indsor или StructureMap: конфигурируем, а затем разрешаем. Т ем не менее, концепция разделения понятий при работе с Autofac гораздо понятнее. ContainerBuilder не умеет разрешать компоненты, и поэтому мы не сможем сконфигурировать контейнер. После прочтения этого раздела вы приобретете полноценное понимание всей сущности паттерна применения Autofac и сможете начать использовать его в сценариях, в которых все компоненты руководствуются должными DI-паттернами, например, Constructor Injection. Давайте начнем с простейшего сценария и посмотрим, как можно разрешать объекты с помощью контейнера Autofac. Разрешение объектов Основная услуга, предоставляемая любым DI-контейнером – разрешение компонентов. В этом разделе мы рассмотрим API, которое позволяет разрешать компоненты при помощи Autofac. Вспоминая обсуждение процесса разрешения компонентов при помощи Castle W indsor и StructureMap, вы, возможно, припомните, что Windsor требует, чтобы перед тем, как стало возможным разрешение компонентов, все соответствующие компоненты должны быть зарегистрированы. В отличие от Castle Windsor StructureMap делает все возможное, чтобы сделать это за нас, когда мы запрашиваем конкретные типы с открытыми конструкторами. Autofac может вести себя и как Castle Windsor, и как StructureMap, хотя по умолчанию его поведение совпадает с поведением Castle Windsor. Перед разрешением компонентов мы должны их зарегистрировать, поэтому одним из самых простых способов применения Autofac является приведенный ниже пример: var builder = new ContainerBuilder(); builder.RegisterType SauceBéarnaise sauce = container.Resolve Как уже было замечено ранее на рисунке 13-2, для конфигурирования компонентов необходим экземпляр ContainerBuilder . В примере выше регистрируется конкретный класс SauceBéarnaise с помощью builder , чтобы при запросе создания контейнера выходной экземпляр container компоновался классом SauceBéarnaise . Все это дает возможность разрешать класс SauceBéarnaise из контейнера. 497 Если компонент SauceBéarnaise не был зарегистрирован, то попытка разрешить его приведет к исключению ComponentNotRegisteredException Если сравнивать этот самый простейший из всех сценариев с аналогичными фрагментами кода Castle Windsor и StructureMap, то Autofac покажется слегка более подробным. Однако вся эта подробность возникает, главным образом, благодаря дополнительно му шагу создания контейнера из ContainerBuilder , поэтому в более крупных и сложных конфигурациях Autofac будет сравним с другими DI-контейнерами. По умолчанию Autofac требует явно регистрировать все соответствующие компоненты. Т акое поведение присуще и Castle W indsor. Если вам необходимо поведение, более похожее на StructureMap, то можно поступить следующим образом: var builder = new ContainerBuilder(); builder.RegisterSource( new AnyConcreteTypeNotAlreadyRegisteredSource()); var container = builder.Build(); SauceBéarnaise sauce = container.Resolve Единственное отличие от предыдущего примера – отсутствие явной регистрации класса SauceBéarnaise . Вместо этого вы регистрируете IRegistrationSource , называемый AnyConcreteTypeNotAlreadyRegisteredSource . Это название довольно трудно произносить, но оно более или менее отражает функцию AnyConcreteTypeNotAlreadyRegisteredSource : он выступает в роли источника регистраций любого конкретного типа, который ранее не был зарегистрирован. При добавлении AnyConcreteTypeNotAlreadyRegisteredSource не нужно явно добавлять тип SauceBéarnaise , поскольку SauceBéarnaise является конкретным классом с открытым конструктором, а источник регистраций может автоматически обеспечивать его регистрацию. И сточники регистраций Дополнительная особенность Autofac – способность предоставлять большее количество дополнительных источников регистраций, нежели при прямом использовании API, которое раскрывается с помощью ContainerBuilder . Это механизм расширяемости, который Autofac использует для реализации различных возможностей, но, поскольку он основан на открытом интерфейсе под названием IRegistrationSource , можно использовать его и как механизм расширяемости. Единственной открытой реализацией IRegistrationSource , которая входит в состав Autofac, является AnyConcreteTypeNotAlreadyRegisteredSource , которую вы уже видели ранее, но в Autofac есть и другие внутренние реализации интерфейса. Идея, лежащая в основе IRegistrationSource , заключается в том, что реализации могут выступать в роли резервных механизмов или более эвристических источников регистраций компонентов, чем те, которые может обеспечить обычное API. Помимо вышеупомянутых источников конкретных типов использовать IRegistrationSource можно также и для того, чтобы превратить Autofac в автоматический m ock-контейнер. Все это выходит за рамки этой книги, и для эффективного использования Autofac нет необходимости более подробно рассматривать IRegistrationSource 498 Фактически, регистрация AnyConcreteTypeNotAlreadyRegisteredSource приводит к тому, что контейнер начинает вести себя как StructureMap, а не как Castle Windsor. Теперь контейнер может не только разрешать конкретные типы, обладающие конструкторами по умолчанию, но и автоматически интегрировать тип с другими конкретными зависимостями, не используя при этом явную регистрацию. Кроме того, сразу после того, как было введено слабое связывание, Autofac необходимо сконфигурировать на преобразование абстракций в конкретные типы. П реобразование абстракций в конкретны е типы Несмотря на то, что время от времени способность Autofac автоматически интегрировать конкретные типы может становиться довольно полезной, обычно слабое связывание подразумевает преобразование абстракций в конкретные типы. Создание экземпляров на основании таких преобразований – ключевая возможность, которую предлагает любой DI- контейнер, но для начала нужно еще определить такие преобразования. В приведенном ниже примере выполняется преобразование интерфейса IIngredient в конкретный класс SauceBéarnaise , что позволяет успешно разрешать IIngredient : var builder = new ContainerBuilder(); builder.RegisterType IIngredient ingredient = container.Resolve Для регистрации типов и определения преобразований используется экземпляр ContainerBuilder . Метод RegisterType дает возможность зарегистрировать конкретный тип. Как вы уже видели в первом примере этой главы, можно остановиться и на этом, если вашей целью является только регистрация класса SauceBéarnaise . Кроме того, при помощи метода As можно продолжить пример и определить, каким образом необходимо регистрировать конкретный тип. П римечание При использовании Autofac мы начинаем работать с конкретным типом, а затем преобразовываем его в абстракцию. Такая последовательно сть полностью противоположна работе с большинством других DI-контейнеров: в них абстракция преобразуется к конкретному типу. П редупреждение По сравнению с Castle Windsor и StructureMap в Autofac, в сущности, отсутствуют ограничители generic-типа между типами, определенными методами RegisterType и As . А это означает, что можно преобразовыват ь несовместимые типы. Код будет компилироваться, но во время выполнения, когда ContainerBuilder будет создавать контейнер, возникнет исключение. В большинстве случаев нам нужно только generic API. Несмотря на то, что generic API, в отличие от других DI-контейнеров, не обеспечивает должную типовую безопасность, это все равно хороший способ конфигурирования контейнера. Однако в некоторых ситуациях необходим и более слабо типизированный способ разрешения сервисов. Это тоже возможно. 499 Разрешение слабо типизированных сервисов В некоторых случаях нельзя использовать generic API, поскольку на этапе проектирования может быть неизвестно, какой тип нужен. Все, что у нас есть – экземпляр Type , но, все же, нам может потребоваться и экземпляр этого типа. Пример такой ситуации приведен в разделе 7.2 "Построение ASP.NET MVC приложений", где мы обсуждали ASP.NET MVC класс DefaultControllerFactory . Соответствующий метод приведен ниже: protected internal virtual IController GetControllerInstance( RequestContext requestContext, Type controllerType); Поскольку в наличии у нас только экземпляр Type , мы не можем использовать дженерики, и должны прибегнуть к слабо типизированному API. В Autofac есть слабо типизированная перегрузка метода Resolve , которая позволяет реализовывать метод GetControllerInstance следующим образом: return (IController)this.container.Resolve(controllerType); Слабо типизированная перегрузка метода Resolve позволяет передавать параметр controllerType прямо в Autofac, но при этом необходимо приводить возвращаемое значение к IController Независимо от используемой перегрузки метода Resolve Autofac гарантирует, что эта перегрузка будет возвращать экземпляр необходимого типа или выдавать исключение в случае отсутствия подходящих зависимостей. После того, как все необходимые зависимости должным образом сконфигурированы, Autofac может автоматически интегрировать необходимый тип. В предыдущем примере this.container – это экземпляр Autofac.IContainer . Для обеспечения возможности разрешать необходимый тип все слабо связанные зависимости необходимо сначала сконфигурировать. Autofac можно конфигурировать несколькими способами. В следующем разделе приведена информация о самых универсальных из этих способов. Конфигурирование ContainerB uilder Как уже говорилось в разделе 3.2 "Конфигурирование DI-контейнеров", существует несколько концептуально разных способов конфигурирования DI-контейнера. На рисунке 13-3 представлен обзор возможных вариантов. 500 Рисунок 13-3: Концептуально разные варианты конфигурирования. Использование кода в качестве конфигурации подразумевает строгую типизированность и имеет тенденцию к явному определению. XML, с другой стороны, предполагает позднее связывание, но тоже склонно к явному определению. Автоматическая регистрация, напротив, полагается на соглашения, которые могут быть и строго типизированными, и более слабо определенными. Будучи DI-контейнером второго поколения, Autofac первоначально не имел в своей основе XML конфигурации, но позднее его API было заменено на программное API конфигурации подобно некоторым наиболее развитым контейнерам. Конечно, его создавали с заложенной в него возможностью эффективно использовать множество различных источников конфигурации, и XML – это один из возможных вариантов. Центральное API конфигурации сконцентрировано на коде и поддерживает как технологию конфигурации в коде, так и автоматическую регистрацию на основании соглашений, а XML остается одним из возможных вариантов. Autofac поддерживает все три подхода и даже позволяет сочетать их в рамках одного и того же контейнера. Поэтому в этом отношении он обеспечивает нас всем, что только могло бы нам понадобиться. В этом разделе описаны способы применения всех трех типов источников конфигурации. Конфигурация в коде В разделе 13.1.1 "Разрешение объектов" уже приводился краткий обзор строго типизированного API конфигурации контейнера Autofac. В этом разделе мы рассмотрим его более подробно. 501 В Autofac в любом типе конфигурации применяется API, раскрываемое классом ContainerBuilder , несмотря на то, что большинство используемых нами методов являются методами расширений. Один из общепринято используемых методов – это метод RegisterType , о котором уже упоминалось ранее: builder.RegisterType Как и при работе с Castle Windsor, регистрация класса SauceBéarnaise в виде IIngredient скрывает конкретный класс так, что разрешать класс SauceBéarnaise с помощью этой регистрации больше невозможно. Т ем не менее, с этим легко можно справиться с помощью перегрузки метода As , которая позволяет определить, что конкретный тип преобразуется к нескольким зарегистрированным типам: builder.RegisterType .As Вместо того чтобы регистрировать класс только в виде IIngredient , вы можете зарегистрировать его и как класс, и как интерфейс, который он реализует. Это позволяет контейнеру разрешать запросы и SauceBéarnaise , и IIngredient Помимо этого еще один вариант – это связать в цепочку вызовы метода Call : builder.RegisterType .As Получаем результат, аналогичный только что рассмотренному примеру. Существует три generic-перегрузки метода As , которые позволяют задать один, два или три типа соответственно. Если необходимо задать большее количество типов, то для этого существует не generic-перегрузка. С помощью нее можно задать любое количество типов. П одсказка Если необходимо с помощью метода As задать более трех типов, то следует рассматривать его как smell-эскиз регистрируемого класса. Если он будет реализовывать столь много интерфейсов, то, скорее всего, он нарушит принцип единичной ответственности. В подлинных приложениях нам всегда приходится преобразовывать более одной абстракции, поэтому приходится конфигурировать несколько преобразований. Делается это при помощи нескольких вызовов метода RegisterType : builder.RegisterType Здесь IIngredient преобразуется в SauceBéarnaise , а ICourse – в Course . Наложение типов не происходит, поэтому вполне очевидно, что будет происходить. Т ем не менее, одну и ту же абстракцию можно зарегистрировать несколько раз: builder.RegisterType В этом примере IIngredient регистрируется дважды. При разрешении IIngredient получаем экземпляр Steak . Выигрывает последняя регистрация, но предыду щие 502 регистрации тоже не забыты. Autofac правильно обрабатывает составные конфигурации одной и той же абстракции, но к этому мы еще вернемся в разделе 13.3. При конфигурировании Autofac можно использовать и дополнительные опции, но полноценно сконфигурировать приложение можно и с помощью продемонстрированных в этом разделе методов. Однако чтобы оградить себя от слишком явного сопровождения конфигурации контейнера, вместо этих методов можно использоват ь другой подход, основанный на соглашениях – автоматическу ю регистрацию. Автоматическая регистрация Чаще всего большинство регистраций будут аналогичны друг другу. Такие регистрации очень утомительно сопровождать, а явная регистрация каждого компонента, возможно, не является самым продуктивным подходом. Рассмотрим библиотеку, в которой содержится множество реализаций интерфейса IIngredient . М ожно выполнять индивидуальну ю конфигурацию каждого класса, но это приведет к многочисленным схожим вызовам метода RegisterType . Что еще хуже, всякий раз при добавлении новой реализации IIngredient необходимо явно регистрировать ее вместе с ContainerBuilder , чтобы она была доступна для использования. Было бы более продуктивно установить, что все реализации IIngredient , найденные в этой сборке, необходимо регистрировать. Это можно выполнить с помощью метода расширения RegisterAssemblyTypes . М етод RegisterAssemblyTypes позволяет указывать сборку и конфигурировать все выбранные классы этой сборки посредством одного единственного оператора. Для получения экземпляра Assembly можно использовать представительский класс. В этом примере таким классом является Steak : builder.RegisterAssemblyTypes(typeof(Steak).Assembly) .As |