Главная страница
Навигация по странице:

  • , и, таким образом, применить аспект при помощи единственной строки декларативного кода

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


    Скачать 5.66 Mb.
    НазваниеРуководство по применению этого механизма. Net приложениях. Книга демонстрирует основные паттерны на обычном языке C#, поэтому вы в полной мере поймете, как работает механизм внедрения зависимостей
    АнкорВнедрение зависимостей в .net
    Дата14.12.2019
    Размер5.66 Mb.
    Формат файлаpdf
    Имя файлаВнедрение зависимостей в .NET.pdf
    ТипРуководство
    #100226
    страница25 из 43
    1   ...   21   22   23   24   25   26   27   28   ...   43
    ProductRepository вы используете?
    Поскольку атрибуты конкретных классов не могут повторно использоваться в рамках реализаций, это привело бы к нарушению принципа "не повторяйся".

    Вы не смогли бы варьировать логику обеспечения безопасности независимо от
    ProductRepository
    Однако идея обращения к сквозным сущностям в декларативной манере не нова.
    Поскольку она часто применяется в аспектно-ориентированно м программировании, она подходит нам только в том плане, что мы более близко рассматриваем саму эту идею и то, как она приводит нас к слабо связанному механизму перехвата.

    348 9.3. Объявление аспектов
    В предыдущих разделах мы рассматривали паттерны механизма перехвата и то, как они могут помочь вам обратиться к сквозным сущностям с помощью SOLID принципов. В разделе 9.2.3 "Добавление функциональности обеспечения безопасности" вы увидели, как можно было сократить объем реализации такого аспекта, как проверка безопасности, до чисто декларативного подхода.
    Применение атрибутов для объявления аспектов является универсальным приемом аспектно-ориентированного программирования (AOP). Но сколь заманчивым бы он ни казался с первого взгляда, использование атрибутов сопровождается некоторого рода встроенными проблемами, которые делают применение атрибутов менее чем идеальным решением. Первую часть данного раздела я буду использоват ь для того, чтобы дать обзор этой сущности и ее малоизвестных недостатков.
    П римечание
    Я периодически использую термин ат рибут аспекта, обозначающий пользовательский атрибут, реализующий или выражающий аспект.
    Поскольку мы, в сущности, отбросили в сторону идею об использовании атрибутов для объявления аспектов, остальную часть данной главы мы проведем за рассмотрением динамического перехвата с помощью DI-контейнера, который предоставляет лучшую альтернативу.
    Использование атрибутов для объявления аспектов
    Атрибуты имеют общую с Decorat or'ами черту: несмотря на то, что они могут добавлять или подразумевать изменение поведения члена, сигнатуру они оставляют неизменной. Как вы видели в разделе 9.2.3 "Добавление функционально сти обеспечения безопасности", вы можете заменить явный, императивный код авторизации на атрибут. Вместо того чтобы написать одну или более одной строки явного кода, вы могли бы достичь того же результата, применяя атрибут
    [PrincipalPermission]
    Довольно заманчиво было бы экстраполировать эту концепцию в другие сквозные сушности. Было бы это здорово, если бы вы могли отметить метод или класс атрибутом
    [HandleError]
    или даже пользовательским атрибутом
    [CircuitBreaker]

    , и, таким образом, применить аспект при помощи единственной строки декларативного кода?
    Возможно, это было бы здорово, но существует несколько проблем, связанных с данным подходом, которые вы должны понять.
    В первую очередь это проблема, которая вырастает из того факта, что атрибуты, по своему существу, являются пассивными. Несмотря на то, что определение пользовательского атрибута и применение его столь же просто, как наследование класса от
    System.Attribute и наделение других классов пользовательским атрибутом, он находится там, ничего при этом не делая.
    Но подождите! Разве атрибут
    [PrincipalPermission]
    не изменил поведение метода? Да, это так, но этот атрибут (и некоторые другие атрибуты, доступные в стандартной

    349 библиотеке классов) – особенный. .NET Fram ework понимает этот атрибут и влияет на него, но .NET Framework не будет так делать для всякого пользовательского атрибута, который вам захотелось бы ввести.
    Если вы хотите разрешить пользовательским атрибутам изменять поведение приложения, то у вас есть два варианта:

    Изменить шаг компиляции

    Ввести пользовательский хост рабочей среды
    Давайте вкратце исследуем каждый вариант.
    Модификация компиляции
    Работа одного из самых популярных фреймворков аспектно-ориентированного программирования, PostSharp, заключается именно в разрешении вам добавлять пользовательские атрибуты в код. Эти атрибуты должны наследоваться от специального атрибута, который определен в PostSharp SDK, предоставляющем виртуальные методы, которые вы можете переопределить для того, чтобы задать желаемое вами поведение аспекта. Затем вы можете применить эти атрибуты к своим классам или членам класса.
    Рисунок 9-8 демонстрирует, что случится потом.
    Рисунок 9-8: Работа PostSharp заключается в добавлении шага посткомпиляции после того, как будет завершена обычная компиляция. Поскольку пользовательские PostSharp атрибуты в вашем коде трактуются обычным компилятором (например, csc.exe) так же, как и любые другие атрибуты, выходной результат – это обычная сборка с пассивными атрибутами. В PostSharp входит шаг посткомпиляции, на котором PostSharp собирает скомпилированну ю сборку и чередует код ваших пользовательских атрибутов напрямую с атрибутивным кодом. Результатом является новая .NET сборка с вложенными аспектами.
    PostSharp полагается на посткомпиляцию для того, чтобы преобразовать пассивные атрибуты в активный код. PostSharp процессор присматривает за атрибутами, унаследованными от PostSharp атрибутов, и чередует код этих атрибутов с кодом, расширенным этими атрибутами. Результат – новая сборка, в которой соответствующий код аспекта чередуется с первоначальным кодом.
    Эта сборка – абсолютно обычная сборка, которая запускается всюду, где запускается весь остальной .NET код. Для этого не требуется никакой специальной рабочей среды.
    Среди преимуществ данного подхода можно отметить тот факт, что для него не требуется никак особых усилий с вашей стороны. Механизм внедрения зависимостей не необходим, хотя и не исключается. Я уверен, что есть и другие преимущества.
    Один из недостатков данного подхода – тот факт, что запускаемый код отличается от написанного вами кода. Если вы захотите отладить код, то вам нужно будет выполнить особые шаги, и, несмотря на то, что поставщик с радостью предоставляет средства,

    350 позволяющие вам выполнить только отладку, он к тому же направляет вас к антипаттерну
    Vendor Lock-In.
    Но самый большой недостаток заключается в использовании самих атрибутов. Этот недостаток идет в одном ряду с использованием пользовательского хоста для активации атрибутов. Давайте сделаем обзор данного варианта перед тем, как рассмотреть недостатки атрибутов.
    И спользование пользовательского хоста
    Еще один вариант активации атрибутов заключается в требовании, чтобы весь код активировался или инициализировался при помощи пользовательского хоста или фабрики. Такая фабрика смогла бы проверять все атрибуты классов, которые она инициализирует, и действовать соответствующим образом.
    Мы знаем данный прием из многочисленных .NET технологий, которые полагаются на атрибуты. Примерами этого приема являются:

    В WCF входит множество атрибутов, например,
    [ServiceContract]
    ,
    [OperationContract]
    и т.д. Эти атрибуты приобретают поведение только, когда вы размещаете сервис в экземпляре
    ServiceHost
    (то же самое для вас делает и IIS).

    ASP.NET MVC дает вам возможность указать, какой HTTP verbs вы допустите с атрибутом
    [AcceptVerbs]
    , а также дает вам возможность обрабатывать исключения с помощью атрибута
    [HandleError]
    , и еще несколько других возможностей. Это возможно, поскольку ASP.NET MVC – это один большой пользовательский хост, и он управляет жизненными циклами своих контроллеров.

    Все .NET фреймворки модульного тестирования, о которых я осведомлен, используют атрибуты для идентификации тестовых сценариев. Фреймворк модульного тестирования инициализиру ет тестовые сценарии и интерпретирует атрибуты, чтобы указать, какой из тестов необходимо выполнить.
    Компоновка объектов с помощью DI-контейнера аналогична этим примерам. Поскольку
    DI-контейнер инициализирует экземпляр ы включенных в него классов, он имеет возможность просматривать каждый класс в поисках пользовательских атрибутов.
    Вас не должно удивлять то, что многие DI-контейнеры обладают возможностями, которые позволяют вам делать именно это. Если вы уже решили использовать DI-контейнер, должны ли вы идти до конца и определять, и применять пользовательские атрибуты?
    Я могу подразумевать только одно преимущество, которое такое поведение дает нам касательно динамического перехвата: поскольку атрибут очень легко обнаружить, даже если он предлагает достаточно привлекательный уровень преобразования данных, вы все равно получаете ценный намек на то, что что-то происходит, нежели тот намек, который дает рассматриваемое вами тело метода.
    Но есть и недостатки применения сквозных сущностей с помощью атрибутов. Эти недостатки являются общими для пост-компиляции и пользовательских хостов.
    Н едостатки атрибутов аспектов
    Насколько бы привлекательной ни казалась возможность реализации аспектов в виде пользовательских атрибутов, существуют некоторые недостатки такого подхода.

    351
    Во-первых, атрибуты компилируются совместно с кодом, который они расширяют. Это означает, что вы не сможете так просто изменить ход своих мыслей. Рассмотрим в качестве примера процесс обработки ошибок. В разделе 9.2.2 "Обработка исключений" вы видели, как можно использовать паттерн проектирования Decorator для того, чтобы реализовать обработку ошибок для любого
    IProductManagementAgent
    . Интересно то, что упомянутый выше
    WcfProductManagementAgent ничего не знает об
    ErrorHandlingProductManagementAgent
    . Как проиллюстрировано на рисунке 9-9, они реализованы даже в разных библиотеках.
    Рисунок 9-9: Как
    ErrorHandlingProductManagementAgent
    , так и
    WcfProductManagementAgent реализуют
    IProductManagementAgent
    , но определены они в двух различных библиотеках. Поскольку сборка
    ProductManagementClient содержит
    Com position Root , она зависит от
    ProductWcfAgent и
    PresentationLogic
    Основная реализация, предложенная
    WcfProductManagementAgent
    , не содержит явной функциональности обработки ошибок, поскольку истинный процесс обработки исключений является контексто-зависимым. В GUI приложениях, например, в W PF приложении, которое мы не так давно использовали в качестве примера, хорошей стратегией может стать диалоговое сообщение, но в консольном приложении вместо диалогового сообщения вы, вероятнее всего, предпочли бы создавать выходной поток ошибок, а автоматизированный сервис мог бы перемещать операцию с целью повторения очереди и продолжать выполнять что-то еще.
    Для того чтобы сохранять свою открытость и гибкость, библиотека
    ProductWcfAgent не должна включать в себя обработку ошибок. Но, если вы применяете атрибут аспекта к
    WcfProductManagementAgent
    (или, что еще хуже, к
    IProductManagementAgent
    ), то вы сильно привязываете этот аспект к реализации (или даже к абстракции). Если вы так

    352 поступаете, то вы навязываете
    WcfProductManagementAgent определенну ю стратегию обработки ошибок, и теряете способность варьировать аспекты независимо от реализации.
    Вторая проблема, связанная с атрибутами аспектов, заключается в том, что, у вас есть лишь ограниченное количество вариантов применения атрибутов, которые могут использоваться только на следующих уровнях:

    Параметры, включая возвращаемые значения

    Члены, например, методы, свойства и поля

    Т ипы, например, классы и интерфейсы

    Библиотеки
    Несмотря на то, что атрибуты аспектов предоставляют вам широкий круг возможностей, вы не можете с легкостью выразить большее количество конфигураций, основанных на соглашениях, например, "Я хочу применить аспект Circuit Breaker ко всем типам, имена которых начинаются с Wcf". Вместо этого вам пришлось бы применить гипотетический атрибут
    [CircuitBreaker]
    ко всем подходящим классам, нарушающим принцип DRY
    (Don't Repeat Yourself – Не повторяйся).
    Последний недостаток атрибутов аспектов – атрибуты должны обладать простым конструктором. Если вам нужно использовать зависимости от аспекта, вы можете сделать это только с помощью Am bient Context. Вы уже видели пример этого в разделе 9.2.3
    "Добавление функциональности обеспечения безопасности", где
    Thread.CurrentPrincipal является Am bient Context. Но данный паттерн в редких случаях является наиболее подходящим, и делает процесс управления жизненным циклом более трудным. К примеру, совместное использование
    ICircuitBreaker в рамках многочисленных W CF клиентов неожиданно становится более сложным.
    Несмотря на все эти недостатки, привлекательно сть атрибутов аспектов заключается в том, что вам приходится реализовывать код аспекта только в одном единственном месте.
    В следующем разделе вы увидите, как можно использовать возможности перехвата DI- контейнеров для того, чтобы достичь этой цели, не связывая при этом сильно атрибуты аспектов.
    Применение динамического перехвата
    До настоящего момента вы видели, как можно использовать Decorator'ы для обращения к сквозным сущностям и их реализации. Данный прием удовлетворяет SOLID принципу, но нарушает DRY принцип. Возможно, это и не видно из примеров данной главы, но применение аспекта путем ручной разработки расширений классов включает в себя огромное количество повторяющегося кода.
    П овторяемость Decorator'ов
    Примеры из разделов 9.1.1 "Пример: реализация аудита" и 9.2.1 "Осуществление перехвата с помощью Circuit Breaker" демонстрируют только репрезентативные методы, поскольку каждый метод реализуется одинаково, а мне не хотелось загружать несколько страниц практически идентичным кодом, так как это отвлекало бы нас от того, что мы рассматриваем. Следующий листинг демонстрирует, как похожи методы
    CircuitBreakerProductmanagementAgent
    . Этот листинг демонстрирует только два метода

    353 интерфейса
    IProductManagementAgent
    , но я уверен, что вы сможете экстраполироват ь их и представить, как выглядит остальная реализация.
    Листинг 9-7: Нарушение DRY принципа
    1.
    public void DeleteProduct(int productId)
    2.
    {
    3.
    this.breaker.Guard();
    4.
    try
    5.
    {
    6.
    this.innerAgent.DeleteProduct(productId);
    7.
    this.breaker.Succeed();
    8.
    }
    9.
    catch (Exception e)
    10.
    {
    11.
    this.breaker.Trip(e);
    12.
    throw;
    13.
    }
    14.
    }
    15.
    public void InsertProduct(ProductEditorViewModel product)
    16.
    {
    17.
    this.breaker.Guard();
    18.
    try
    19.
    {
    20.
    this.innerAgent.InsertProduct(product);
    21.
    this.breaker.Succeed();
    22.
    }
    23.
    catch (Exception e)
    24.
    {
    25.
    this.breaker.Trip(e);
    26.
    throw;
    27.
    }
    28.
    }
    Строка 6, 20: Единственное отличие
    Т ак как вы уже видели метод
    InsertProduct в листинге 9-4, цель данного примера кода – проиллюстрировать повторяющу юся сущность Decorat or'ов, используемых в виде аспектов. Единственное отличие между методами
    DeleteProduct и
    InsertProduct
    – это то, что каждый из них вызывает свой собственный соответствующий метод расширенного агента.
    Даже если мы успешно делегировали реализацию Circuit Breaker отдельному классу посредством интерфейса
    ICircuitBreaker
    , этот код инфраструктуры явно нарушает DRY принцип. Он может быть склонен к разумной неизменности, но все равно обязателен.
    Всякий раз, когда вам хочется добавить новый член к типу, который вы расширяете, или когда вам хочется применить Circuit Breaker к новой абстракции, вы должны применять этот же самый код инфраструктуры.
    В качестве одного из способов решения данной проблемы вы могли бы рассматривать применение таких генераторов кода, как Text T em plate Transformation Toolkit (T4) от
    Visual Studio, но многие DI-контейнеры предлагают лучший вариант решения этой проблемы – посредством динамического перехвата.

    354
    Автоматизация Decorator'ов
    Код в каждом методе листинга 9-7 очень похож на шаблон. Самая сложная составляющая процесса реализации Decorator'а в виде аспекта – проектирование этого шаблона, но после того, как шаблон спроектирован, остальное – это уже просто механический процесс:

    Создать новый класс

    Унаследовать от нужного интерфейса

    Реализовать каждый член интерфейса путем применения шаблона
    Данный процесс является настолько механическим, что вы можете использовать какой- либо инструмент для его автоматизации. Такой инструмент использовал бы рефлексию и схожие API для того, чтобы найти все члены, которые нужно реализовать, а затем применял бы шаблон ко всем этим членам. Рисунок 9-10 демонстрирует, как эту процедуру можно применить с помощью T4 шаблона.
    Рисунок 9-10: T 4 делает возможной автоматическую генерацию кода Decorator'а из шаблонов. Стартовая точка – прототип шаблона, который понимает основную концепцию
    Decorator'а. Прототип шаблона содержит код генерации кода, который будет генерировать границы расширяемого класса, но не определяет никакого кода аспекта. Из прототипа шаблона разрабатывается шаблон аспекта, который описывает, как должен применяться конкретный аспект (например, Circuit Breaker) при расширении любого интерфейса.
    Результатом является специализированный шаблон (Som eAspect.tt) этого конкретного аспекта, который можно использовать для генерации Decorator'ов конкретных интерфейсов. Результат – обычный файл кода (SomeAspectDecorator.cs), который обычно компилируется вместе с другими файлами кода.
    Несмотря на то, что генераторы кода позволяют вам справляться с симптомами повторяющегося кода, они все равно на своем пути оставляют большое количество повторяющегося кода. Если вы верите, что код обязателен, то больший объем кода приведет к большим затратам, независимо от того, автоматически он генерируется или нет.
    Даже если вы не купитесь на этот аргумент, у вас все еще есть статический набор авто- генерируемых Decorator'ов. Если вам понадобится новый Decorator для данной комбинации аспекта и абстракции, то вы должны явным образом добавить данный класс.
    Он может генерироваться автоматически, но вам все равно нужно не забыть создать его и присоединить. Более условный подход в данном случае не возможен.
    Некоторые DI-контейнеры предоставляют вам вариант, который лучше автоматически генерируемого кода: автоматически генерируемые классы. Возможно, разница между этими вариантами кажется скрытой, но продолжайте читать дальше.

    355
    Динамический перехват
    Одной из множества значительных возможностей .NET Framework является возможность динамически порождать типы. Помимо автоматической генерации кода во время проектирования, также возможно написать код, который порождает полнофункциональный класс во время выполнения. Такой класс не имеет основообразующего файла исходного кода, но компилируется напрямую из некоторой абстрактной модели.
    Таким же образом, как вы можете автоматизировать генерацию Decorator'ов в файлах исходного кода, вы можете автоматизировать генерацию Decorator'ов, которые порождались бы прямо во время процесса выполнения. Как демонстрирует рисунок 9-11, это как раз то, что и позволяет вам выполнить динамический перехват.
    Примечание
    Не все DI-контейнеры поддерживают перехват во время выполнения; если вам нужна данная возможность, позаботьтесь о том, чтобы ваш DI-контейнер был подобран соответствующим образом.
    Рисунок 9-11: Некоторые DI-контейнеры позволяют нам определять аспекты в виде перехватчиков. Перехватчик – фрагмент кода, который реализует аспект и взаимодействует с контейнером. Регистрация перехватчика с помощью контейнера позволяет контейнеру динамически создавать и порождать Decorator'ы, которые содержат поведение аспекта. Эти классы существуют только во время выполнения.
    Для применения динамического перехвата вам все равно нужно написать код, который реализует аспект. Это мог бы быть код инфраструктуры, необходимый для аспекта Circuit
    Breaker, что продемонстрировано в листинге 9-7. Как только вы написали этот код, вы должны сообщить DI-контейнеру об этом аспекте и о том, когда ему следует его применять.
    Во время выполнения DI-контейнер будет динамически порождать новые классы в выполняемо м AppDomain, основанном на зарегистрированных аспектах. Самое лучшее в данном подходе – это то, что вы можете использовать конфигурацию на основе соглашений для того, чтобы определить, как применяются аспекты, и можете решить использовать разные соглашения в разных приложениях (например, несмотря на то, что вы можете совместно использовать множество библиотек, в WPF приложении и
    PowerShell приложении у вас могут быть разные стратегии обработки ошибок).

    356
    Примечание
    В аспектно-ориентированном программировании соглашение, которое сопоставляет аспекты с классами и членами, называется Pointcut.
    Хватит теории – давайте рассмотрим пример.
    Пример: перехват с помощью Windsor
    Благодаря их повторяющемуся коду аспекты Circuit Breaker и обработчик ошибок из разделов 9.2.1 "Осуществление перехвата с помощью Circuit Breaker" и 9.2.2 "Обработка исключений" являются отличными кандидатами для динамического перехвата. В качестве примера давайте рассмотрим, как можно получить DRY, SOLID код с помощью возможностей перехвата Castle Windsor.
    Примечание
    Вместо Castle Windsor я мог бы выбрать и другой DI-контейнер, но определенно не любой. Некоторые DI-контейнеры поддерживают механизм перехвата, а остальные не поддерживают – в части 4 рассматриваются возможности конкретных DI-контейнеров.
    В данном примере вы будете реализовывать и регистрировать перехватчики как для обработчика ошибок, так и для Circuit Breaker. Добавление аспекта в Windsor – это процесс, состоящий из трех шагов, как это продемонстрировано на рисунке 9-12.
    Рисунок 9-12: Т ри шага, составляющие процесс добавления аспекта в Windsor
    В данном примере вы будете выполнять эти шаги для обоих аспектов. Обработку ошибок реализовать проще всего, поскольку у нее нет зависимостей; давайте начнем с этого аспекта.
    Реализация перехватчика обработки ошибок
    Реализация перехватчика для W indsor требует от нас реализации интерфейса
    IInterceptor
    , который имеет всего один метод. Следующий листинг демонстрирует, как реализовать стратегию обработки ошибок из листинга 9-5, но в отличие от листинга 9-5 следующий листинг демонстрирует весь класс.
    Листинг 9-8: Реализация перехватчика обработки ошибок
    1.
    public class ErrorHandlingInterceptor : IInterceptor
    2.
    {
    3.
    public void Intercept(IInvocation invocation)
    4.
    {
    5.
    try
    6.
    {
    7.
    invocation.Proceed();
    8.
    }

    357 9.
    catch (CommunicationException e)
    10.
    {
    11.
    this.AlertUser(e.Message);
    12.
    }
    13.
    catch (InvalidOperationException e)
    14.
    {
    15.
    this.AlertUser(e.Message);
    16.
    }
    17.
    }
    18.
    private void AlertUser(string message)
    19.
    {
    20.
    var sb = new StringBuilder();
    21.
    sb.AppendLine("An error occurred.");
    22.
    sb.AppendLine("Your work is likely lost.");
    23.
    sb.AppendLine("Please try again later.");
    24.
    sb.AppendLine();
    25.
    sb.AppendLine(message);
    26.
    MessageBox.Show(sb.ToString(), "Error",
    27.
    MessageBoxButton.OK, MessageBoxImage.Error);
    28.
    }
    29.
    }
    Строка 1: Реализация
    IInterceptor
    Строка 5-6, 8-16: Реализация аспекта
    Строка 7: Вызов расширенного метода
    Строка 18: Демонстрация диалогового окна
    Для того чтобы реализовать перехватчик, вы должны выполнить наследование от интерфейса
    IInterceptor
    , определенного Windsor. Нужно реализовать всего один метод, и вы делаете это путем применения того же самого кода, который вы не раз использовали, когда реализовывали
    ErrorHandlingProductManagementAgent
    Единственное отличие от листинга 9-5 – вместо того, чтобы делегировать вызов метода конкретному методу, вы должны обобщить все, поскольку применяете этот код к потенциально любому методу. Вы даете Windsor указание, позволить вызову пройти дальше к расширенной строке, вызывая для этого метод
    Proceed для входного параметра invocation
    Интерфейс
    IInvocation
    , переданный в метод
    Intercept в качестве параметра, представляет собой вызов метода. К примеру, он мог бы представлять вызов метода
    InsertProduct
    . Метод
    Proceed
    – один из ключевых членов данного интерфейса, поскольку он дает нам возможность позволить вызову пройти к следующей реализации стека.
    Интерфейс
    IInvocation также дает вам возможность установить выходное значение перед тем, как разрешить вызову пройти дальше, а также обеспечивает доступ к подробной информации о вызове метода. Из параметра invocation можно получить информацию об имени метода и значениях параметра, а также множество другой информации о текущем вызове метода.
    Реализация перехватчика – сложный шаг. Следующий шаг – полегче.

    358
    Ре гистрация перехватчика обработки ошибок
    Перехватчик необходимо зарегистрировать с помощью контейнера до того, как его можно будет использовать. Данный шаг не устанавливает правила, управляющие тем, как и когда активизируется перехватчик (Pointcut), а только делает его доступным в виде компонента.
    П римечание
    Вы можете рассматривать этот шаг как формальность для соответствия требованиям
    W indsor. Одна из причуд Windsor заключается в том, что каждый компонент должен быть зарегистрирован явным образом, даже когда он является конкретным типом, обладающим конструктором по умолчанию. Не все DI-контейнеры работают таким образом, но в
    W indsor такое поведение преднамеренно.
    Зарегистрировать класс
    ErrorHandlingInterceptor легко (
    container
    – это экземпляр
    IWindsorContainer
    ): container.Register(Component.For());
    Регистрация класса
    ErrorHandlingInterceptor ничем не отличается от регистрации любого другого компонента с помощью Windsor, и вы даже могли бы решить использовать подход, основанный на соглашениях для того, чтобы зарегистрировать все реализации
    IInterceptor
    , обнаруженные в конкретной сборке. Возможно, это похоже на шаблонный код из раздела 3.2 "Конфигурирование DI-контейнеров".
    Последний шаг активации перехватчика – определить правила того, когда и как применяется этот перехватчик, но поскольку эти правила также должны затрагивать перехватчик Circuit Breaker, мы отложим этот шаг до тех пор, пока также не будет готов и другой перехватчик.
    Ре ализация перехватчика C ircuit Breaker
    Перехватчик Circuit Breaker несколько сложнее, поскольку для него необходима зависимость
    ICircuitBreaker
    , но как демонстрирует следующий листинг, вы решаете этот вопрос путем применения стандартного Constructor Injection. Когда дело доходит до компоновки класса, Windsor обращается с ним как с любым другим компонентом: так как он может разрешать зависимость, то все в порядке.
    Листинг 9-9: Реализация перехватчика Circuit Breaker
    1.
    public class CircuitBreakerInterceptor : IInterceptor
    2.
    {
    3.
    private readonly ICircuitBreaker breaker;
    4.
    public CircuitBreakerInterceptor(
    5.
    ICircuitBreaker breaker)
    6.
    {
    7.
    if (breaker == null)
    8.
    {
    9.
    throw new ArgumentNullException(
    10.
    "breaker");
    11.
    }
    12.
    this.breaker = breaker;
    13.
    }
    14.
    public void Intercept(IInvocation invocation)
    15.
    {

    359 16.
    this.breaker.Guard();
    17.
    try
    18.
    {
    19.
    invocation.Proceed();
    20.
    this.breaker.Succeed();
    21.
    }
    22.
    catch (Exception e)
    23.
    {
    24.
    this.breaker.Trip(e);
    25.
    throw;
    26.
    }
    27.
    }
    28.
    }
    Строка 3-13: Constructor Injection
    Строка 16-18, 20-26: Реализация аспекта
    Строка 19: Вызов расширенного метода
    Для
    CircuitBreakerInterceptor нужна зависимость
    ICircuitBreaker
    , а внедрение зависимостей в
    IInterceptor выполняется с помощью Constructor Injection, как и в любых других сервисах.
    Как вы видели в листинге 9-8, вы реализуете интерфейс
    IInterceptor путем применения шаблона, предложенного предыдущей, повторяющейся реализацией из листинга 9-4. Еще раз вместо вызова конкретного метода вы вызываете метод
    Proceed для того, чтобы дать перехватчику указание, позволить обработке продолжить свое выполнение для следующего компонента стека Decorator.
    К данному моменту вы должны начать понимать формирование паттерна. Вместо того чтобы повторять код инфраструктуры Circuit Breaker для каждого метода абстракции, вы можете определить его всего один раз в перехватчике.
    Т акже вам необходимо зарегистрировать класс
    CircuitBreakerInterceptor с помощью контейнера; так как у класса есть зависимость, для этого потребуется не одна, а две строки кода.
    Ре гистрация перехватчика Circuit Breaker
    Для перехватчика обработки ошибок нужна была только одна строка кода регистрации, но, поскольку
    CircuitBreakerInterceptor зависит от
    ICircuitBreaker
    , вы должны зарегистрировать и эту зависимость: container.Register(Component
    .For()
    .ImplementedBy()
    .DependsOn(new
    { timeout = TimeSpan.FromMinutes(1)
    })); container.Register(Component.For());
    Вы преобразуете интерфейс
    ICircuitBreaker в конкретный класс
    CircuitBreaker
    , для которого нужен такой параметр конструктора, как время ожидания.

    360
    Когда оба перехватчика находятся на своих местах, единственное, что нам еще нужно – определить правила того, когда они будут активизироваться.
    Активация перехватчиков
    К настоящему моменту перехватчики реализованы и зарегистрированы с помощью контейнера Windsor, но вам еще нужно определить, когда они будут активироваться. Если вы не сделаете это, они останутся всего лишь пассивными регистрациями в контейнере и даже не будут вызываться.
    Вы можете рассматривать этот шаг как аналог применения атрибутов аспектов. Если мы применяем к методу гипотетический атрибут
    [CircuitBreaker]
    , то мы соединяем аспект
    Circuit Breaker с этим методом. Определение и применение пользовательских атрибутов – один из способов, с помощью которых мы можем активизировать перехватчиков Windsor, но у нас еще есть несколько других, более подходящих доступных вариантов.
    Самый гибкий – реализовать и зарегистрировать интерфейс
    IModelInterceptorsSelector
    . Это дает нам возможность написать императивный код, который решает, какой перехватчик к каким типам и членам применять. Поскольку мы можем написать условно сложный код, мы имеем возможность применить наши аспекты гораздо более основанным на соглашениях способом.
    В следующем листинге вы используете простую реализацию такого Pointcut.
    Листинг 9-10: Реализация Pointcut
    1.
    public class ProductManagementClientInterceptorSelector :
    2.
    IModelInterceptorsSelector
    3.
    {
    4.
    public bool HasInterceptors(ComponentModel model)
    5.
    {
    6.
    return typeof(IProductManagementAgent)
    7.
    .IsAssignableFrom(model.Service);
    8.
    }
    9.
    public InterceptorReference[]
    10.
    SelectInterceptors(ComponentModel model,
    11.
    InterceptorReference[] interceptors)
    12.
    {
    13.
    return new[]
    14.
    {
    15.
    InterceptorReference
    16.
    .ForType(),
    17.
    InterceptorReference
    18.
    .ForType()
    19.
    };
    20.
    }
    21.
    }
    Строка 6-7: Применение перехватчиков к
    IProductManagementAgent
    Строка 15-18: Возврат перехватчиков
    Интерфейс
    IModelInterceptorsSelector руководствуется паттерном T ester-Doer.
    W indsor сначала вызовет метод
    HasInterceptors
    , чтобы узнать, имеет ли данный компонент, который он собирается инициализировать, какие-либо перехватчики. В данном примере вы отвечаете на этот вопрос положительно, при этом компонент

    361 реализует интерфейс
    IProductManagementAgent
    , но вы могли бы написать условно сложный код, если бы захотели реализовать более эвристический подход.
    Когда метод
    HasInterceptors вернет значение true
    , будет вызван метод
    SelectInterceptors
    . Благодаря этому методу вы возвращаете ссылки на перехватчиков, которые вы уже зарегистрировали. Обратите внимание, что вы возвращаете не экземпляр ы перехватчиков, а ссылки на перехватчики, которые вы уже реализовали и зарегистрировали.
    Это позволяет Windsor автоматически интегрировать любые перехватчики, которые могут иметь свои собственные зависимости (например,
    CircuitBreakerInterceptor из листинга 9-9).
    Знаете, что! Вам еще нужно зарегистрировать класс
    ProductManagementClientInterceptorSelector с помощью контейнера. Это делается немного по-другому, но все равно вкладывается в одну строку кода: container.Kernel.ProxyFactory.AddInterceptorSelector( new ProductManagementClientInterceptorSelector());
    Регистрация
    ProductManagementClientInterceptorSelector окончательно активизирует перехватчиков так, что когда вы разрешаете приложение с помощью Windsor, они автоматически активизируются там, где они должны активизироваться.
    Вы можете подумать, что этот многостраничный анализ перехватчиков Windsor довольно сложен, но вам следует кое-что иметь ввиду:

    Вы реализовали два, не один, перехватчика.

    Я повторил некоторый фрагмент кода из предыдущих примеров для того, чтобы показать, как его можно приспособить. Не важно, решите ли вы писать Decorat or'ы вручную, использовать фреймворк аспектно-ориентированного программирования или использовать динамический перехват, вам всегда нужно будет писать код, который реализует аспект.
    Динамический перехват предоставляет множество преимуществ. Он позволяет нам иметь дело со сквозными сущностями, соблюдая при этом и SOLID, и DRY принципы. Он дает нам по-настоящему слабо связанные аспекты и возможность применять соглашения или сложные эвристические правила для определения того, когда и где какие аспекты применять. Это максимальный уровень свободы и гибкости.
    Вы можете беспокоиться о результатах исполнения компиляции и порождения пользовательских типов "на лету", но насколько я смог определить, Windsor делает это только один раз и повторно использует данный тип для последующих вызовов. Я сделал несколько неформальных отметок, не регистрируя никакого значительного ухудшения исполнения.
    Еще одна проблема – добавленный уровень преобразования. Вы могли бы поспорить, что, применяя атрибуты аспектов, мы все равно оставляем в основном методе заметный след того, что поведение, изменяющее аспекты, приходится к месту. Благодаря Decorator'ам и динамическому перехвату в частности такой след отсутствует. Начинающие разработчики могли бы теоретически напороться на это полу-магическое поведение, завязнув в нем на несколько дней, пока кто-нибудь не поможет им, объяснив концепцию.

    362
    В некоторых инфраструктурах это является реальной проблемой. Подумайте, как бы вы справились с этой проблемой, если бы решили применить динамический перехват.
    9.4. Резюме
    Механизм внедрения зависимостей по-настоящему расцветает, когда дело доходит до применения объектно-ориентированных принципов, например, SOLID. В частности, слабо связанная природа механизма внедрения зависимостей позволяет нам использовать паттерн Decorator для того, чтобы соблюдать принцип открытости/закрытос ти, а также принцип единственной ответственности. Эта возможность ценна в широком круге ситуаций, поскольку позволяет нам оставлять наш код чистым и хорошо- структурированным, но применяется особенно хорошо, когда дело доходит до сквозных сущностей.
    Сквозные сущности традиционно принадлежат области аспектно-ориентированного программирования, но также могут с большим успехом разрешаться с помощью механизма внедрения зависимостей. Паттерн проектирования Decorator – это основной паттерн, позволяющий нам обертывать существующую функциональнос ть в дополнительные уровни поведения, не изменяя первоначальный код.
    Но, несмотря на свои многочисленные достоинства, главная проблема, связанная с реализацией Decorat or'ов, – это то, что они склонны к многословности и повторяемости.
    Даже если вы можете соблюдать SOLID принципы проектирования, мы прекращаем нарушать DRY принцип, поскольку нам нужно писать снова и снова один и тот же код инфраструктуры – для каждого члена каждого интерфейса, который мы хотим расширить с помощью конкретного аспекта.
    Кажется, что атрибуты похожи на привлекательную альтернативу Decorator'ов, поскольку они позволяют нам добавлять аспекты более сжатым способом. Но, поскольку атрибуты компилируются в коде, который они расширяют, они приводят к сильному связыванию, и их применения нужно избегать.
    Некоторые DI-контейнеры предлагают более привлекательну ю альтернативу благодаря возможности динамически порождать Decorator'ы во время выполнения. Эти динамические Decorator'ы обеспечивают перехват, который соблюдает как SOLID, так и
    DRY принципы.
    Интересно отметить, что динамический перехват – единственная особенность DI- контейнеров, которая не имеет прямого эквивалента в Poor Man's DI. С этой точки зрения, в части 3 вы видели, как обращаться с композицией объектов и механизмом управления жизненным циклом с помощью благоразумного применения паттернов, но когда дело доходит до механизма перехвата, самое близкое, что мы получаем – это множество
    Decorator'ов.
    Несмотря на то, что понятие Decorator сравнимо с механизмом перехвата, предпочтителен скачок от множества закодированных вручную Decorat or'ов к одному, а также DRY перехватчик. Скачок от множества Decorator'ов к одному может привести к вспышке повторяющегося кода инфраструктуры, тогда как использование DRY перехватчика предлагает возможность обращения к сквозным сущностям с помощью нескольких строк кода и в качестве дополнительного бонуса – возможность использовать применение аспектов на основе соглашений.

    363
    Именно здесь, в заключении части 3, мы, наконец, достигаем той области, где DI- контейнеры бесспорно оставляют Poor Man's DI позади. Даже без механизма перехвата
    DI-контейнер может гораздо лучше управлять сложностью, вложенной в процесс преобразования абстракций к конкретным типам, а также управлять их жизненными циклами; но когда мы добавим к этому сочетанию механизм перехвата, мы уже не сможем разрушить комбинацию.
    На этой ноте мы можем с радостью оставить Poor Man's DI за частью 3 и перейти к чтению информации о конкретных DI-контейнерах в части 4.

    364
    DI-контейнеры
    Предыду щие части этой книги были посвящены разнообразным принципам и паттернам, которые совместно определяют механизм внедрения зависимостей. Как объяснялось в главе 3, DI-контейнер – это необязательный инструмент, который можно использовать для реализации большого количества общецелевой инфраструктуры, которую вам пришлось бы реализовать иным образом, если бы вы использовали Poor Man's DI.
    На протяжении всей книги я стремился по возможности сохранять ее, независимой от контейнеров. Не принимайте это в качестве рекомендации Poor Man's DI; наоборот, я хотел, чтобы вы увидели механизм внедрения зависимостей в его истинной форме, незапятнанным API какого-либо конкретного контейнера (возможно, необычным).
    Есть небольшая причина того, чтобы потратить свое время на изучение Poor Man's DI, так как в .NET доступны многие превосходные DI-контейнеры. В данной части 4 представлен набор из 6 конкретных бесплатных контейнеров. В каждой главе я предоставляю детальный обзор API конкретного контейнера, как он относится к масштабам, охватываемым в части 3, а также другие разнообразные проблемы, которые являются причинами бед начинающих разработчиков.
    В части 4 рассматриваются такие контейнеры, как Castle Windsor, StructureMap,
    Spring.NET, Autofac, Unity и MEF. Существуют и другие фреймворки контейнеров, которые я, по той или иной причине, не включил в данную книгу: Ninject, Hiro, Funq,
    LinFu, OCInject и т.д. Пожалуйста, обратите внимание на то, что содержание данной книги само по себе не является предвестником одобрения. Несмотря на то, что многие из включенных в эту книгу DI-контейнеров обладают превосходным качеством, это не было единственным критерием отбора. Существуют конкретные контейнеры, которые я не включил в эту книгу всего лишь по причине той доли, в которой они представлены на рынке. Между тем я решил описать остальные, поскольку они обеспечивают отличный контраст благодаря их различным философиям проектирования и целям. Это также означает, что мне пришлось исключить некоторые превосходные контейнеры. Мне, конечно, хотелось бы включить в эту книгу все DI-контейнеры, но, увы, поскольку мое пространство ограниченно, это не возможно.
    Каждая глава руководствуется общим шаблоном. Чтение одного и того же предложения по 6 раз может дать вам определенное ощущение дежавю, но я считаю это достоинством, поскольку такая структура глав должна дать вам возможность быстро находить аналогичные разделы в рамках разных глав, если вы захотите сравнить, как конкретная деталь раскрывается в рамках двух или более контейнеров. Для быстрого сравнения обращайтесь к следующей таблице:
    DI-контейнер Достоинства
    Недостатки
    Castle
    W indsor
    Завершенность
    Понимает Decorator
    Т ипизированные фабрики
    Доступно коммерческое сопровождение
    Местами извилистое API

    365
    DI-контейнер Достоинства
    Недостатки
    StructureMap
    Просто работает в большинстве случаев
    Не поддерживает механизм перехвата
    Spring.NET
    Механизм перехвата
    Исчерпывающая документация
    Доступно коммерческое сопровождение
    Очень завязан на XML
    Не подерживает API, основанное на соглашениях
    Не поддерживает пользовательские жизненные циклы
    Ограниченные возможности автоматической интеграции
    Autofac
    Простое для изучения API
    Доступно коммерческое сопровождение
    Не поддерживает механизм перехвата
    Частично поддерживает пользовательские жизненные циклы
    Unity
    Механизм перехвата
    Хорошая документация
    Неизменное API
    Небольшие возможности управления жизненным циклом
    Не подерживает API, основанное на соглашениях
    MEF
    Доступен в .NET
    4/Silverlight 4 BCL
    Коммерческое сопровождение
    Не настоящий DI-контейнер
    Конфигурация, основанная на статических атрибутах
    Не поддерживает XML
    Не поддерживает технологию использования кода в качестве конфигурации
    Не подерживает API, основанное на соглашениях
    Не поддерживает пользовательские жизненные циклы
    Не поддерживает механизм перехвата
    Многие из описанных здесь контейнеров – это проекты с открытым исходным кодом, имеющие быстрые циклы выпуска. На момент написания книги информация, представленная в части 4, была актуальна, но всегда старайтесь также консультироваться с более современными источниками.
    Эти главы рассматриваются в качестве стимула. Если вы еще не подобрали тот контейнер, который вам больше всего нравится, то можете прочитать эти шесть глав, чтобы сравнить все контейнеры, но можете прочитать одну или две главы, которые действительно вас интересуют.

    366 10. Castle W indsor
    11. StructureMap
    12. Spring.NET
    13. Autofac
    14. Unity
    15. M EF

    367 10. Castle Windsor
    Меню:

    Знакомство с Castle Windsor

    Управление жизненным циклом

    Работа с составными компонентами

    Конфигурирование сложных API
    В предыдущих девяти главах мы обсуждали паттерны и принципы, которые применяются к механизму внедрения зависимостей в общем, но за исключением нескольких примеров мы еще не рассматривали подробно то, как применять эти принципы и паттерны при использовании конкретного DI-контейнера. В этой главе вы увидите, как все эти общие паттерны применяются к Castle Windsor; для того чтобы эта глава принесла вам полноценную пользу, вам нужно ознакомиться с материалом предыдущих глав.
    Castle Windsor – второй по старшинству DI-контейнер .NET . Он является частью более крупного проекта с открытым исходным кодом, известного как Castle Project, который предоставляет повторно используемые библиотеки для различных целей. Windsor является составляющей DI-контейнера в Castle Project, но может использоваться и независимо от любых других компонентов Castle. В этой главе мы рассмотрим его как отдельный компонент.
    Помимо того, что он является одним из самых старых DI-контейнеров, Castle Windsor еще и один из самых зрелых контейнеров, и, если верить нескольким совсем не научным интернет-опросам, один из самых популярных контейнеров. Несмотря на то, что начать работать с Windsor достаточно легко, он предлагает богатое и расширяемое API.
    В данной главе мы попутешествуем по Castle Windsor. Когда мы завершим свой тур, вы должны будете уже иметь достаточные знания об этом контейнере, чтобы суметь незамедлительно применять его. Мы не собираемся рассматривать продвинутые сценарии расширяемости, а вместо этого сконцентрируемся на главных паттернах применения.
    Рисунок 10-1 демонстрирует структуру главы.
    Рисунок 10-1: Структура данной главы похожа на дерево. Первый раздел знакомит вас с контейнером Castle Windsor и объясняет, как сконфигурировать и разрешить компоненты.
    Остальные разделы, базирующиеся на введении, можно читать последовательно или более или менее независимо. В последнем разделе используется синтаксис и несколько методов, которые в первый раз встречаются в разделе, посвященном составным компонентам, поэтому, если вы захотите пропустить предпоследний раздел, можете случайно вернуться к нему снова.

    368
    Первый раздел обеспечивает полноценное знакомство с Castle W indsor и демонстрирует, как конфигурировать и разрешать компоненты. Следующие три раздела имеют дело с паттернами применения, которые требуют излишнего внимания; вы можете прочитать их все по порядку или пропустить некоторые и читать только те, которые вас интересуют.
    Данная глава должна дать вам возможность приступить к работе, а также коснуться наиболее универсальных проблем, которые могут возникнуть при повседневном использовании Castle Windsor. Глава 10 не является законченной трактовкой Castle
    W indsor – на рассмотрение его могла бы уйти вся эта книга.
    Вы можете прочитать эту главу изолированно от остальных глав части 4, чтобы изучить конкретно Castle W indsor, или можете прочитать ее совместно с другими главами, чтобы сравнить DI-контейнеры. Цель данной главы – показать, какое отношение Castle Windsor имеет к паттернам и принципам, описанным в предыдущих девяти главах, и как он их реализует.
    10.1. Знакомство с Castle Windsor
    10.2. Управление жизненным циклом
    10.3. Работа с составными компонентами
    10.4. Конфигурирование сложных API
    10.5. Резюме

    369 10.1. Знакомство с Castle Windsor
    Из этого раздела вы узнаете, где можно взять Castle Windsor, что вы при этом получите и как начать его использовать. Кроме того, мы рассмотрим универсальные варианты конфигурации, а также то, как упаковать настройки конфигурации в повторно используемые компоненты. В таблице 10-1 содержится фундаментальная информация, которая, скорее всего, понадобится вам для того, чтобы приступить к работе.
    Как показывает рисунок 10-2, в Castle Windsor присутствует простая цикличность: конфигурирование контейнера путем добавления компонентов, а затем разрешение необходимых компонентов.
    Рисунок 10-2: Общий паттерн применения Castle W indsor прост: сначала мы конфигурируем контейнер, затем разрешаем компоненты из этого контейнера. В большинстве случаев мы создаем экземпляр
    WindsorContainer и полностью конфигурируем его перед тем, как начать разрешать компоненты из этого контейнера. Мы разрешаем компоненты из того экземпляра, который конфигурируем.
    Таблица 10-1: Краткая информация о Castle Windsor
    Вопрос
    Ответ

    1   ...   21   22   23   24   25   26   27   28   ...   43


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