Содержание


Аспектно-ориентированное программирование (АОП): Для чего его лучше использовать?

Comments

illustrationНедавно мне предложили провести обсуждение аспектно-ориентированного программирования (АОП) с исследовательской группой программной инженерии (SERG). За несколько часов до встречи один из студентов спросил меня: «Ну и чем хороши аспекты? Только не надо приводить пример регистрации. Похоже, это единственное применение аспектов, о котором я знаю».

Этот вопрос заставил меня задуматься об эффективных способах применения АОП в некоторых программных системах, с которыми я работал. Я также понял, что необходимо подумать о том, как и когда следует начинать использовать новые методы, особенно, если для них требуются новые способы мышления. АОП, о котором упоминалось выше в этой статье, как раз и представляет новый подход. Хотелось бы немного поговорить о способах эффективного, на мой взгляд, использования АОП. Также рассмотрим некоторые последние достижения в АОП, способные облегчить его понимание.

В качестве примеров будет использоваться аспектно-ориентированный язык Java. В настоящее время имеется несколько языков программирования с аспектно-ориентированной реализацией. Среди них AspectC++ и даже AspectL, то есть аспектно-ориентированная реализация Lisp.1

Обзор концепций АОП

При отсутствии опыта работы с АОП имеется множество ознакомительных статей, включая мою статью, опубликованную в феврале 2004 г2. 2 В большинстве, если не во всех, вводных курсов по АОП для иллюстрации концепции аспектов приводится пример регистрации. (Большинство пользователей понимают, что такое регистрация. Это хороший пример возможного использования АОП). Аспекты - это пересекающиеся (crosscutting) отношения. Поэтому аспекты непросто инкапсулировать в отдельный класс. Кроме того, если строго следовать объектно-ориентированной парадигме, необходимо представить такие отношения в единой, удобной для сопровождения форме. При этом часто пересекающаяся ответственность передается в отдельный класс helper и зависит от каждого класса, требующего функциональности, выраженной отношением к включению вызовов для передачи в соответствующие места. Довольно сложно гарантировать, что разработчики будут одинаково вставлять регистрацию в соответствующие точки кода. Аспекты предоставляют механизм, хотя и несовершенный, для улучшения такого положения.

Чтобы понять данное обсуждение АОП, необходимо знать несколько концепций. Основная концепция - точка соединения (join point). Эта концепция, с которой знакомы все программисты, и для которой нет названия. Точка соединения представляет собой «точно определенную точку в процессе выполнения программы».3 Имеется много типов точек соединения, например, вызов или возврат метода, который может быть обычным возвратом или генерацией исключения.

В АОП необходимо найти способ идентификации точек соединения в программе. В AspectJ для описания одной или нескольких точек соединения используется pointcut (срез точки). Pointcut представляет собой выражение, описывающее набор точек соединения. Pointcut можно рассматривать как запрос к коду, возвращающий набор точек соединений.

При выборе набора точек соединений для них предоставляется механизм advice (рекомендация). Advice является исполняемым кодом, который запускается при достижении точки соединения в процессе выполнения программы. Точки соединения, pointcuts и advice предназначены для реализации динамических свойств ПО. Механизм advice изменяет характеристики исполняемого кода программы.

Имеется одна концепция, учитывающая статическую природу системы. Это объявление inter-type. Объявления inter-type позволяют изменять статическую структуру программы. Можно добавлять методы и переменные и изменять иерархию наследования в соответствии с определенными правилами.

Так как класс является единицей модульности для Java, аспект является дополнительной единицей модульности для AspectJ. Аспект инкапсулирует точки соединения, pointcuts, объявления inter-type и advice (рекомендации) для пересекающегося отношения. АОП не заменяет объектно-ориентированный анализ и проектирование. АОП основывается на объектно-ориентированной парадигме и используется в тех случаях, когда объектно-ориентированный подход не обеспечивает нужное решение.

Примеры АОП

Теперь рассмотрим примеры, где можно использовать АОП и где оно используется. Некоторые примеры взяты из производственных систем, другие - из процесса эксплуатации и разработки. Начнем с пары примеров для разработчиков.

Трассировка выполнения

Удивительно, как много разработчиков вставляет в код разновидности операторов print для отладки или трассировки выполнения программы. Для отладчиков эта информация действительна важна. Но данное обсуждение не связано с изучением отладчиков. Естественно, существуют веские доводы для создания текстовой трассировки программы. В текущем наборе средств разработки AspectJ (AJDT) в Eclipse имеется прекрасный пример аспекта, реализующего трассировку выполнения программ. Пример подробно описан в справочной системе Eclipse. Его также можно найти в «Руководстве по программированию AspectJ».

В примере имеется небольшое Java-приложение с классами, представляющими двухмерные фигуры, например, круги и квадраты. В приложении также имеется метод, создающий два круга и квадрат и выводящий их свойства, например, площадь, периметр и расстояние между центрами. После запуска программы вывод данных должен соответствовать рисунку 1.

Рис. 1. Данные программы формы

Рис. 1. Данные программы формы

Рисунок 1. Данные программы формы

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

Пример трассировки состоит из нескольких версий программы для трассировки с использованием аспектов. Рассмотрим последнюю версию. Описание других версий см. в «Руководстве по программированию AspectJ».

Решение для надежного механизма трассировки состоит из двух файлов. Первый файл - абстрактный аспект. Этот аспект похож на абстрактный класс, где некоторая часть кода оставлена программистам для реализации в производном классе, или, в данном случае, в производном аспекте. Абстрактный аспект, называемый Trace, имеет несколько стандартных методов для вывода сообщений о входе и выходе из метода или конструктора и для форматирования выходных данных. Если аспекты не используются, эти методы находятся в классе helper и используются для вывода информации о трассировке. Трассировка также позволяет программистам задавать тип трассировки с помощью задания свойства TRACELEVEL. Существует три уровня: нет сообщений, сообщения, которые не структурируются, и сообщения, структурированные для отображения вложенных вызовов.

Трассировка определяет три pointcut: два конкретных и один абстрактный (см. рис. 2). Любой аспект, расширяющий аспект Trace, должен предоставить абстрактный pointcut, myClass. Назначение pointcut заключается в выборе классов для объектов, содержащих точки соединений, которые будут рекомендованы. Это позволяет разработчикам решить, какие классы следует включить в выводные данные трассировки.

Рис. 2. Pointcut в аспекте трассировки

Рис. 2. Pointcut в аспекте трассировки

Рисунок 2. Pointcut в аспекте трассировки

Pointcut myConstructor выбирает точки соединения в начале любого конструктора для объекта в классе, выбранного myClass. Точка соединения фактически является телом конструктора. myMethod подобен myConstructor, но выбирает выполнение любого метода в выбранном классе. Обратите внимание, что выполнение метода toString пропускается, так как он используется в коде advice.

Предоставляемый аспектом код advice довольно прост. Имеется код advice (рекомендации), вставляемый перед каждой точкой соединения, и advice, выполняемый после точки соединения. Это показано на рисунке 3.

Рис. 3. Pointcut в аспекте трассировки

Рис. 3. Pointcut в аспекте трассировки

Рисунок 3. Pointcut в аспекте трассировки

Для использования аспекта Trace его необходимо расширить и предоставить точную реализацию для абстрактного pointcut. На рисунке 4 показано тело примера программы с аспектом TraceMyClasses. Pointcut используется для выбора только тех объектов, которые являются экземплярами классов TwoDShape, Circle или Square. Основной метод задает TRACELEVEL, инициализирует трассировку выполнения программы и запускает основной метод примера.

Рисунок 4. Определенный аспект трассировки

Рисунок 4. Определенный аспект трассировки

Рисунок 4. Определенный аспект трассировки

На рисунке 5 показана часть выходных данных для данного примера. Обратите внимание, что выводится информация обо всех объектах. Это часть метода toString каждого объекта. Рointcut (срез точки) myClasses публикует объект в advice (рекомендация), которая позволяет легко добавлять информацию об объекте.

Рисунок 5. Пример выходных данных трассировки

Рисунок 5. Пример выходных данных трассировки

Рисунок 5. Пример выходных данных трассировки

Каковы преимущества АОП подхода к трассировке в сравнении с ручной вставкой кода трассировки в нужных местах? Таких преимуществ несколько.

  • Весь исходный код, связанный с отношением трассировки, хранится в одном месте (два аспекта);
  • Код трассировки легко вставлять и удалять. Для этого из конфигурации конструкции удаляются аспекты;
  • Код трассировки можно добавить в любое место, даже при добавлении к целевым классам новых методов. Это позволяет устранить ошибки программиста. При удалении аспекта из конфигурации конструкции можно быть уверенным, что весь код трассировки удален и ничего не пропущено;
  • Получаем повторно используемый аспект, который можно применять и улучшать.

Контрактное или безопасное программирование

Концепцию контрактного программирования (Design by Contract) ввел Бертран Майер (Bertrand Meyer).4 Согласно этому принципу разработчик класса и пользователь этого класса имеют общие допущения по реализации класса. Контракт включает инварианты, предварительные условия и постусловия. Контрактное программирование позволяет разработчику классов сконцентрироваться на логике, реализующей функциональность класса, не беспокоясь о достоверность аргументов. Разумеется, если контракт содержит предварительные условия для аргументов. Контрактное программирование позволяет избежать создания дополнительного кода и улучшить производительность до тех пор, пока все клиенты класса придерживаются контракта.

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

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

Создадим аспект для проверки всех аргументов в публичных методах. Прежде всего создадим pointcut (срезы точек). Будем использовать pointcut myClass из предыдущего примера и добавим pointcut для выбора конструкторов, требующих проверки аргументов, и метода расстояния, который не должен вызываться со значением NULL. На рисунке 6 показан набор необходимых pointcut. Обратите внимание, что второй pointcut определяет в качестве цели pointcut экземпляр TwoDShape. Это значит, что этот pointcut выбирает в объектах только вызовы метода расстояния.

Рисунок 6. Pointcut (срезы точек) для проверки аргументов

Рисунок 6. Pointcut (срезы точек) для проверки аргументов

Рисунок 6. Pointcut (срезы точек) для проверки аргументов

Наконец, необходимо добавить соответствующие advice (рекомендации). Для простоты выведем сообщение при обнаружении неверного аргумента, а затем, в случае theconstructor, изменим фактические значения на ноль и проигнорируем вызов метода расстояния при передаче значения NULL. На рисунке 7 показаны два таких элемента advice.

Рисунок 7. Элемент advice (рекомендации) проверки аргументов

Рисунок 7. Элемент advice (рекомендации) проверки аргументов

Рисунок 7. Элемент advice (рекомендации) проверки аргументов

При попытке выполнить следующее предложение:

Circle c3 = new Circle(3.0,2.0,-2.0);

c1.distance(null>);

в программе, получает следующее сообщение:

Negative argument encountered at: execution(tracing.Circle(double, double, 
	double)) All arguments changed to 0.0
Null value given to distance at: 
	call(double tracing.Circle.distance(TwoDShape))

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

В больших проектах, где много классов и используется несколько интерфейсов, код для аспектов, реализующих проверку аргументов, можно организовать в отдельной папке. Могу представить несколько способов организации аспектов для их простой идентификации и обслуживания. При создании системы для внутренних целей можно использовать конфигурацию внутренней конструкции, при создании системы для внешних клиентов - конфигурацию, включающую аспекты. Eclipse AJDT позволяет просто создавать новые конфигурации конструкций.

Аспекты и шаблоны проектирования

Шаблоны проектирования стали правилом хорошего тона в программировании. АОП предоставляет способ улучшения существующих и использования новых шаблонов. Фактически, ввод кода для пересекающихся отношений представляет собой в некотором роде шаблон. В настоящее время некоторые исследователи оценивают реализацию шаблонов проектирования с использованием АОП-подхода. Ян Ханнеманн (Jan Hannemann) из Университета Британской Колумбии исследовал тему в своей кандидатской диссертации . На его Web-сайте http://www.cs.ubc.ca/~jan/AODPs/можно загрузить код, реализующий шаблоны группы GOF.5.5 Николас Лесецки (Nicholas Lesiecki) также писал о шаблонах проектирования для Web-сайта IBM developerWorks.6 В его статье приводится более полное описание в сравнении с данной статьей.

Рассмотрим очень простой пример реализации стандартного шаблона проектирования Adapter в AspectJ.

На рисунке 8 показана диаграмма Unified Modeling Language (UML) для шаблона Adapter. В этом шаблоне клиенту требуется служба и для этого создается запрос. Может иметься много поставщиков службы, и каждый из них может иметь различное имя службы или какие-либо нестандартные требования, которые должны соблюдаться запрашивающей стороной. Хорошее объектно-ориентированное проектирование предполагает, что запрос службы инкапсулируется в целевой интерфейс, программу для интерфейса и, при необходимости, создает адаптер, действующий как посредник между клиентом и службой (на диаграмме это Adaptee).

Рисунок 8. Шаблон адаптера

Рисунок 8. Шаблон адаптера

Рисунок 8. Шаблон адаптера

Подход с использованием адаптера кажется вполне разумным и целесообразным. Но что делать в случае устаревшей системы, не предназначенной для работы с шаблонами или адаптерами? Разработчики этих систем не реализовали возможность будущего изменения. Вызовы службы могут быть разбросаны во всем приложении. Как реализовать новую улучшенную службу? Без аспектов, вероятно, придется восстанавливать исходный код системы, искать все вызовы службы, разрабатывать адаптер и реализовывать шаблон Adapter. В зависимости от количества вызовов старой службы это может стать довольно утомительной задачей. Восстановление исходного кода и его улучшение - достойная цель, но позволить такую роскошь можно далеко не всегда. Приходится довольствоваться выделением кода, это лучшее, что можно сделать в сжатые сроки.

В этом случае можно создать АОП версию шаблона Adapter, немедленно решающую проблему и позволяющую сделать шаг вперед к более организованной и лучше спроектированной системе.

На рисунке 9 показан простой клиент, использующий службу. Служба возвращает расстояние от одной точки до текущего объекта Client.

Рисунок 9. Простой клиент

Рисунок 9. Простой клиент

Рисунок 9. Простой клиент

Интерфейс для службы описывает отдельный метод:

double useService(Client clt, double x, double y);

Новый улучшенный поставщик службы имеет метод с немного отличным названием и другими параметрами. Его интерфейс:

double useNewService(double x0, double x1, double y0, double y1);

Нам нужно создать адаптер, который располагается между вызовами старой службы, получает соответствующие аргументы для новой службы, вызывает новую службу и возвращает результаты клиенту. Клиент при этом не изменяется. Аспект, необходимый для этого, показан на рисунке 10.

Рисунок 10. Аспект Adapter для вызова новой службы

Рисунок 10. Аспект Adapter для вызова новой службы

Рисунок 10. Аспект Adapter для вызова новой службы

Обратите внимание на простоту аспекта. Объявим pointcut для идентификации каждого вызова метода useService исходной службы. Затем воспользуемся advice (рекомендацией) around для замены этого вызова на вызов новой службы после извлечения из вызывающего клиента необходимой информации.

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

Основной недостаток состоит в том, что старый клиент по-прежнему остается в системе, даже если он не используется. При наличии времени (которого, похоже, нет никогда) наиболее вероятно, что придется возвратиться и восстановить исходный код системы для использования стандартного шаблона Adapter. Самое малое, можно изменить метод useService исходной службы на код, возвращающий фиктивное значение, например, 0,0, так как этот метод никогда не будет вызываться.

Применение на профессиональном уровне

До сих пор рассматривались четко ограниченные, но все же полезные примеры применения АОП. Возникает вопрос, а насколько хорошо этот метод масштабируется. Укажу и кратко опишу один известный пример. Более подробное рассмотрение потребует написание другой статьи.

Возможно, некоторые из вас знакомы с Spring Framework,7 представляющую собой прикладную инфраструктуру, поддерживающую разработку корпоративных приложений. Spring Framework предоставляет многоуровневую среду J2EE для создания комплексных корпоративных приложений. АОП является одной из основных технологий, используемых в Spring. В сообществе Spring разработана собственная аспектно-ориентированная реализация, позволяющая применять к коду комплексную логику во время выполнения, а не во время компиляции, как это происходит в AspectJ. Но при этом имеются функции интеграции, позволяющие легко включить AspectJ в Spring Framework.

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

Что можно сделать дальше?

На мой взгляд, в будущем АОП станет частью набора инструментальных средств разработчика программного обеспечения. Не знаю, доберемся ли мы до момента, когда будем создавать системы с использованием АОП в качестве основного механизма проектирования, но думаю, мы найдем способы улучшить с помощью аспектов существующие объектно-ориентированные проекты. Ускорить процесс могут несколько моментов.

Во-первых, необходимы стандартные стабильные реализации АОП. Этот процесс уже начался. Версия 5 AspectJ объединяет два наиболее популярных языка АОП Java - AspectJ и AspectWerkz. Также помогают стандартные реализации в других языках, например, C++.

Во-вторых, необходимо разработать метрики, позволяющие обосновывать эффективность применения АОП к отдельным системам. Например, будет ли повторная реализация шаблонов проектирования с использованием АОП лучше стандартных объектно-ориентированных шаблонов? В каких случаях шаблоны лучше, а в каких - нет? Какие необходимо создать показатели для получения информации о таких метриках и для их разработки? Время простого подхода, когда объектно-проектированная (или любая другая выбранная вами) система считалась хорошей, (), прошло. Необходимо создать проект и решения по реализации на основе эмпирических данных. 9

В-третьих, необходимо продолжить разработку инструментов, поддерживающих АОП. Я рад сообщить, что новый AJDT для Eclipse представляет собой замечательный набор инструментов. В этих инструментах улучшена поддержка, необходимая для эффективного и рационального использования аспектов. Инструментальные средства должны также поддерживать новую технологию, использующую аспекты.

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

Примечания

1 Данные об AspectC++ можно найти на http://www.aspectc.org/, об AspectL - http://common-lisp.net/project/closer/aspectl.html

2 См. Bertrand Meyer, Object-Oriented Software Construction, 2d ed. Prentice Hall 1998.

3 Группа GOF - Эрих Гамма (Erich Gamma), Ричард Хелм (Richard Helm), Ральф Джонсон (Ralph Johnson) и Джон Влиссидес (John Vlissides), написавшие книгу Design Patterns. Это основополагающая книга о шаблонах проектирования;

4 См. Улучшенные шаблоны проектирования AspectJ, часть 1

5 См. http://www.springframework.org/

6 Для дополнительной информации о Spring Framework рекомендуется использовать книгу Pro Spring Роба Харропа (Rob Harrop) и Яна Мачацека (Jan Machacek), Apress, 2005 г.;

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


Ресурсы для скачивания


Комментарии

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

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=40
Zone=Rational
ArticleID=151307
ArticleTitle=Аспектно-ориентированное программирование (АОП): Для чего его лучше использовать?
publish-date=03152006