Постшарп. Аспектно-Ориентированное Программирование Против Внедрения Зависимостей

В моем разговор с Андресом Хейлсбергом Говоря о страхах, неуверенности и сомнениях по поводу аспектно-ориентированного программирования, я упомянул общую путаницу и непонимание того, что АОП и DI являются конкурирующими концепциями.

В этой статье я постараюсь объяснить вам все различия и сходства этих двух подходов.

Недавно я прекрасно провел время, читая статью Дино: " Аспектно-ориентированное программирование, перехват и единство 2.0 ", в декабрьском выпуске журнала MSDN Magazine. Это отличная статья, и я настоятельно рекомендую всем разработчикам, участвующим в разработке .

NET, прочитать ее полностью.

Как и многие DI-фреймворки и некоторые основные фреймворки (WCF, ASP.NET MVC) , Unity предлагает функцию, подобную АОП: перехватчики.



ПостШарп.
</p><p>
 Аспектно-ориентированное программирование против внедрения зависимостей



Что такое внедрение зависимостей?

Внедрение зависимостей — это архитектурный шаблон, предназначенный для уменьшения зависимостей между компонентами.

Одним из преимуществ уменьшения зависимостей (перехода от сильно связанной архитектуры к слабосвязанной архитектуре) является снижение уровня осведомленности компонентов друг о друге.

Это обеспечивает лучшее разделение задач с использованием принципа проектирования «программа к интерфейсу, а не к реализации».

Разработчики C# и Java часто неправильно интерпретируют этот шаблон проектирования: клиенты обычно работают с интерфейсами (то, что вы объявляете с помощью ключевого слова интерфейса), а не с классами.

Я предпочитаю интерпретировать это так: «программировать по контракту, а не по реализации».

Контракт на использование класса в этом случае состоит из открытых членов класса, снабженных документацией (плюс их предварительные и постусловия, если применимо).

Если быть еще точнее: «программа, основанная на документации, а не на классах».

Итак, если ваша цель — просто лучше разделить концепции, вам не так уж нужны интерфейсы.

Все, что вам нужно, это ключевые слова «public», «internal», «protected» и «private».

Важно отметить, что в объектно-ориентированном проектировании интерфейсы не являются догмой.

Не все разработчики фреймворков придерживаются этой философии: «библиотека классов Java в большей степени основана на интерфейсах, чем .

NET, просто потому, что разработчики .

NET имеют негативный опыт использования COM-интерфейсов» Программирование на основе интерфейсов (а не классов) полезно, когда ваш компонент должен работать с несколькими реализациями интерфейса.

Существует бесчисленное множество случаев, когда это будет очень полезно (в конце концов, мы все хотим, чтобы наша архитектура была расширяемой), но давайте посмотрим правде в глаза: во многих случаях, когда нашему компоненту необходимо взаимодействовать с другим компонентом, мы прекрасно знаем последующую реализацию, и мы прекрасно знаем, что никакой другой реализации не предвидится.

Так зачем тогда использовать его (интерфейс) здесь? Есть два варианта, когда вы можете использовать концепцию программы на основе интерфейса:

  • Вы зависите от компонента, который вам не предоставили, но у вас есть необходимость начать разработку, вы не можете ждать.

    Что еще более важно, вы должны сейчас тестировать свой собственный код. Вам нужно издеваться над этим компонентом до тех пор, пока он не будет доставлен вам, только в целях тестирования.

  • Вы зависите от компонента, имеющего постоянное состояние (например, базы данных), какого-либо реального процесса (например, системы обработки банковских карт) или просто зависите от медленного или ненадежного ресурса (веб-сервиса).

    Вам необходимо смоделировать эти компоненты с помощью собственной реализации только в целях тестирования.

В таких ситуациях (которые на самом деле случаются только при тестировании) на самом деле хорошо, что компоненты не связаны в исходном коде, даже если мы знаем, как они будут зависеть друг от друга при окончательной установке.

Так как же нам связать эти компоненты вместе? Именно здесь в игру вступает внедрение зависимостей.

С помощью шаблона проектирования «Внедрение зависимостей» вы указываете «контейнеру», как объекты должны быть связаны друг с другом, и заставляете этот «контейнер» связывать объекты друг с другом.

На самом деле вы не создаете объекты классов с помощью конструкторов, вы делаете это с помощью «контейнерных» методов.

Контейнер решает, какая реализация вам нужна, создает объект и возвращает его вам.



Аспектно-ориентированное программирование

Внедрение зависимостей решает проблему связывания слабосвязанных компонентов друг с другом.

Аспектно-ориентированное программирование решает широкий спектр проблем.

Действительно, в реальности, когда различные функции и возможности (такие как аудит, ведение журналов, обработка транзакций) влияют практически на все бизнес-требования, АОП выражает эту сложную реальность в форме компактного решения с минимальным дублированием кода.

Когда мы хотим провести код-ревью, аудит нашего кода, нашим естественным желанием будет просмотреть только один файл.

Однако когда мы пересматриваем код, который относится к некоторым управленческим требованиям заказчика, мы не хотим видеть этот код. Как вы понимаете, этого невозможно добиться, используя только объектно-ориентированное программирование, и это послужило причиной возникновения и развития аспектно-ориентированного программирования.



Сходства и различия между АОП и DI
Между DI и AOP есть некоторые сходства:
  • Оба достигают слабой связи в архитектуре.

  • Оба обеспечивают лучшее разделение функций.

  • Оба снимают некоторые проблемы с основного кода.

Однако DI и АОП существенно различаются в ситуациях, когда они полезны:
  • DI хорош, когда у вас есть зависимость от компонентов, и вас не волнует, как они реализованы;
  • АОП хорош, когда вам нужно применить какое-то поведение к большому количеству элементов кода.

    Более того, целевой код не обязательно зависит от такого поведения.



Динамические прокси

Итак, как же внедрение зависимостей стало ассоциироваться с аспектно-ориентированным программированием? Просто шаблон проектирования DI позволяет легко добавлять к компонентам новое поведение.

Вот как это делается: когда вы запрашиваете зависимость от контейнера, вы ожидаете получить реализацию интерфейса.

Вы можете только догадываться, какую реализацию вы получите, но часто вы играете с этим в игру.

Если вы попросите контейнер добавить какое-то поведение (например, отслеживание вызовов) к компоненту, вы не получите сам объект. Вместо этого вы получите динамический прокси-объект. Прокси-объект будет находиться между вами и запрошенным компонентом и будет реализовывать запрошенное вами поведение.

А поскольку структуры DI имеют возможность вставлять поведение между клиентом и реализацией компонента, многие понимают DI как одну из технологий, которые могут позволить вам разрабатывать в рамках AOP.

АОП на основе прокси: что хорошо?
Что мне нравится в подходе на основе прокси, так это то, как АОП вписывается в концепцию DI. DI помогает вам настроить контейнер, как он будет создавать компоненты, используя зависимости между объектами, устанавливая их свойства и вызывая методы инициализации.

Настраивать аспекты можно просто как некоторые зависимости в XML-файле или с помощью кода C#, т.е.

конструкция и сборка компонентов будут унифицированы.

Возможность добавлять аспекты просто путем изменения файлов конфигурации может оказаться очень полезной функцией.

Предположим, где-то есть какая-то проблема.

И вы хотите отслеживать все вызовы какого-то компонента.

Это можно сделать без перекомпиляции всего приложения.

Это может быть решающим преимуществом в среде выполнения, где недоступны адекватные инструменты отладки.

Где на поиск проблемы и ее решение может уйти несколько месяцев.



АОП на основе прокси: что не так?
АОП на основе прокси, реализованный с помощью DI-фреймворков, имеет большое количество ограничений: Они
  • работать только с компонентами, реализованными через интерфейс.

    Они не работают со всеми внутренними методами компонента, будь то общедоступные или частные, статические или экземплярные.

    Помните, что компонент является частью приложения и, как правило, включает в себя множество классов;

  • Они будут работать только в тех случаях, когда клиентский код получает ссылку на компонент из контейнера.

    Аспекты не будут применяться, если компонент вызывает свои собственные методы или компонент вызывается без прямого взаимодействия через контейнер;

  • Поскольку аспекты применяются во время выполнения, нет возможности выполнять проверки во время компиляции.

    Фреймворки времени сборки, такие как PostSharp, будут выдавать вам ошибки о неправильном использовании аспектов во время компиляции и могут даже предоставить разработчику аспектов разные способы сделать это правильно (например, вы не можете добавить аспект кэширования к методам, которые возвращают поток, или к методам, имеющим аспекты, связанные с безопасностью);

  • Трудно понять (а) какие аспекты были применены к частям кода, с которыми вы работаете, и (б) к каким частям кода, с которыми вы работаете, были применены аспекты.

    Полноценные платформы, такие как PostSharp или AspectJ, могут показать вам всю информацию прямо в IDE. Что помогает существенно улучшить понимание исходного кода.

  • АОП во время выполнения имеет гораздо худшую производительность, чем платформы времени сборки.

    Это может иметь незначительное значение, если вы применяете аспекты к небольшому количеству грубо спроектированных классов.

    Однако если вы создадите динамические прокси для сотен классов, вы понесете значительные затраты на производительность;

  • Динамические прокси всегда были лишь сторонней функцией сред DI и никогда не реализовывали полную концепцию, даже если это технически осуществимо.

    Они всегда ограничены в своей реализации в методах, и это не идет ни в какое сравнение с тем, что могут предложить PostSharp и AspectJ.



АОП на основе прокси: что страшного?
Когда разработчик начинает что-то практиковать постоянно, не отвлекаясь на конкурирующие технологии, это становится риском, потому что человек, постоянно что-то практикующий, чувствует себя в безопасности.

И практика становится догмой.

Это происходит в TDD, что заставляет людей писать тесты на бессмысленном уровне детализации.

То же самое и с Dependency Injection, где люди настаивают на изоляции всех классов друг от друга.

Помните, что DI изначально был разработан для изоляции компонентов, а не классов.

Компоненты — это части приложения, его блоки, а не объекты.

Меня беспокоит то, что нас вынуждают (под давлением общества) писать гораздо более сложные приложения только для того, чтобы удовлетворить требования тестирования, которые на самом деле не приводят к повышению качества конечного продукта.

Важно помнить, что модульное тестирование — это инструмент, а не цель.

Конечная цель состоит в том, чтобы ваше приложение работало хорошо на протяжении всей своей жизни.



Заключение

Аспектно-ориентированное программирование и внедрение зависимостей — это совершенно разные концепции, но существует ограниченное количество случаев, когда они хорошо сочетаются друг с другом.

В таких ситуациях имеет смысл использовать АОП внутри DI. Что касается других вариантов, у вас есть только два варианта: злоупотреблять динамическим прокси (и это не решение) или использовать специализированный инструмент для разработки АОП.

И эта дилемма касается не только меня, PostSharp или .

NET. Такая же дилемма висит и над Java-программистами, которые думают, что выбрать: Spring AOP или AspectJ. АОП и DI не являются конкурирующими технологиями.

Вы можете использовать их вместе в одном проекте.

Однако важно знать и понимать, почему была создана каждая технология, и сосредоточиться на качестве вашего кода.

Ссылки:

Теги: #aop #di #aop #postsharp #aspectj #.

NET

Вместе с данным постом часто просматривают:

Автор Статьи


Зарегистрирован: 2019-12-10 15:07:06
Баллов опыта: 0
Всего постов на сайте: 0
Всего комментарий на сайте: 0
Dima Manisha

Dima Manisha

Эксперт Wmlog. Профессиональный веб-мастер, SEO-специалист, дизайнер, маркетолог и интернет-предприниматель.