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

В вычислительной , аспект-ориентированное программирование ( АОП ) является парадигма программирования , которая направлена на повышение модульности , позволяя разделение сквозных проблем . Он делает это, добавляя дополнительное поведение к существующему коду ( совет ) без изменения самого кода, вместо этого отдельно указывая, какой код модифицируется с помощью спецификации « pointcut », например, «записывать все вызовы функций, когда имя функции начинается с 'set '. ". Это позволяет добавлять в программу поведение, не являющееся центральным для бизнес-логики (например, ведение журнала), не загромождая ядро ​​кода функциональными возможностями.

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

Аспект-ориентированное программирование регистрация требует Ломая логику программы на отдельные части (так называемые проблемы , связные области функциональности). Почти все парадигмы программирования поддерживают некоторый уровень группировки и инкапсуляции проблем в отдельные независимые объекты, предоставляя абстракции (например, функции, процедуры, модули, классы, методы), которые можно использовать для реализации, абстрагирования и объединения этих проблем. Некоторые проблемы «пересекают» несколько абстракций в программе и бросают вызов этим формам реализации. Эти проблемы называются сквозными или горизонтальными.

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

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

История

АОП имеет несколько прямых предшественников A1 и A2: протоколы отражения и метаобъектов , предметно-ориентированное программирование , фильтры композиции и адаптивное программирование.

Грегор Кичалес и его коллеги из Xerox PARC разработали явную концепцию АОП, а затем разработали расширение AspectJ AOP для Java. Исследовательская группа IBM предпочла инструментальный подход к языковому дизайну и в 2001 году предложила Hyper / J и среду управления проблемами , которые не получили широкого распространения.

В примерах в этой статье используется AspectJ.

Transaction Server Microsoft считается первым крупным применение АОП с последующим Enterprise JavaBeans .

Мотивация и основные понятия

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

Например, рассмотрим банковское приложение с концептуально очень простым методом перевода суммы с одного счета на другой:

void transfer(Account fromAcc, Account toAcc, int amount) throws Exception {
  if (fromAcc.getBalance() < amount)
      throw new InsufficientFundsException();

  fromAcc.withdraw(amount);
  toAcc.deposit(amount);
}

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

Версия со всеми этими новыми проблемами, для примера, могла бы выглядеть примерно так:

void transfer(Account fromAcc, Account toAcc, int amount, User user,
    Logger logger, Database database) throws Exception {
  logger.info("Transferring money...");
  
  if (!isUserAuthorised(user, fromAcc)) {
    logger.info("User has no permission.");
    throw new UnauthorisedUserException();
  }
  
  if (fromAcc.getBalance() < amount) {
    logger.info("Insufficient funds.");
    throw new InsufficientFundsException();
  }

  fromAcc.withdraw(amount);
  toAcc.deposit(amount);

  database.commitChanges();  // Atomic operation.

  logger.info("Transaction successful.");
}

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

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

АОП пытается решить эту проблему, позволяя программисту выражать сквозные проблемы в автономных модулях, называемых аспектами . Аспекты могут содержать совет (код, соединенный с указанными точками в программе) и объявления типов (структурные элементы, добавленные в другие классы). Например, модуль безопасности может включать совет, который выполняет проверку безопасности перед доступом к банковскому счету. Срез точки определяет время ( точки соединения ) , когда один может получить доступ к учетной записи в банке, а код в Определяют тела советов , как осуществляется проверка безопасности. Таким образом, и чек, и места могут храниться в одном месте. Кроме того, хороший pointcut может предвидеть более поздние изменения программы, поэтому, если другой разработчик создаст новый метод для доступа к банковскому счету, совет будет применяться к новому методу при его выполнении.

Итак, для приведенного выше примера, реализующего ведение журнала в аспекте:

aspect Logger {
  void Bank.transfer(Account fromAcc, Account toAcc, int amount, User user, Logger logger)  {
    logger.info("Transferring money...");
  }

  void Bank.getMoneyBack(User user, int transactionId, Logger logger)  {
    logger.info("User requested money back.");
  }

  // Other crosscutting code.
}

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

Модели точек соединения

Связанный с советами компонент аспектно-ориентированного языка определяет модель точки соединения (JPM). JPM определяет три вещи:

  1. Когда совет может работать. Они называются точками соединения, потому что они являются точками в работающей программе, где может быть полезно объединить дополнительное поведение. Чтобы точка соединения была полезной, она должна быть адресуемой и понятной для обычного программиста. Он также должен быть стабильным при несущественных изменениях программы, чтобы аспект был стабильным при таких изменениях. Многие реализации АОП поддерживают выполнение методов и ссылки на поля в качестве точек соединения.
  2. Способ определения (или количественной оценки ) точек соединения, называемый pointcuts . Pointcuts определяет, совпадает ли данная точка соединения. Большинство полезных языков pointcut используют синтаксис, подобный базовому языку (например, AspectJ использует сигнатуры Java), и допускают повторное использование посредством именования и комбинации.
  3. Средство указания кода для запуска в точке соединения. AspectJ вызывает этот совет и может запускать его до, после и вокруг точек соединения. Некоторые реализации также поддерживают такие вещи, как определение метода в аспекте другого класса.

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

Модель точки соединения AspectJ

  • Точки соединения в AspectJ включают вызов или выполнение метода или конструктора, инициализацию класса или объекта, доступ для чтения и записи полей, обработчики исключений и т. Д. Они не включают циклы, супервызовы, предложения throws, несколько операторов и т. Д.
  • Точечные срезы задаются комбинациями примитивных точечных обозначений (PCD).

    «Родственные» PCD соответствуют определенному типу точки соединения (например, выполнение метода) и имеют тенденцию принимать в качестве входных данных сигнатуру, подобную Java. Один из таких pointcut выглядит так:

     execution(* set*(*))
    

    Этот pointcut соответствует точке соединения метода и выполнения, если имя метода начинается с " set" и имеется ровно один аргумент любого типа.

    «Динамические» PCD проверяют типы времени выполнения и связывают переменные. Например,

      this(Point)
    

    Этот pointcut соответствует случаю, когда выполняемый в данный момент объект является экземпляром класса Point. Обратите внимание, что неполное имя класса можно использовать с помощью обычного поиска типа Java.

    PCD «области действия» ограничивают лексическую область действия точки соединения. Например:

     within(com.company.*)
    

    Этот pointcut соответствует любой точке соединения любого типа в com.companyпакете. Это *одна из форм подстановочных знаков, которая может использоваться для сопоставления многих вещей с помощью одной подписи.

    Pointcuts могут быть составлены и названы для повторного использования. Например:

     pointcut set() : execution(* set*(*) ) && this(Point) && within(com.company.*);
    
    Этот pointcut соответствует точке соединения выполнения метода, если имя метода начинается с " set" и thisявляется экземпляром типа Pointв com.companyпакете. На него можно ссылаться, используя имя " set()".
  • Advice указывает запускать в (до, после или около) точки соединения (указанной с помощью pointcut) определенного кода (указанного как код в методе). Среда выполнения AOP автоматически вызывает Advice, когда pointcut совпадает с точкой соединения. Например: after (): set () {Display.update (); } Это фактически определяет: «если set()pointcut совпадает с точкой соединения, запускать код Display.update()после завершения точки соединения».

Другие потенциальные модели точек соединения

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

  • Точки соединения - это все элементы модели.
  • Pointcuts - это логическое выражение, объединяющее элементы модели.
  • Средства воздействия на эти точки - это визуализация всех совпадающих точек соединения.

Межтиповые объявления

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

  aspect DisplayUpdate {
    void Point.acceptVisitor(Visitor v) {
      v.visit(this);
    }
    // other crosscutting code...
  }

Этот фрагмент кода добавляет acceptVisitorметод в Pointкласс.

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

Реализация

Программы АОП могут влиять на другие программы двумя разными способами, в зависимости от базовых языков и сред:

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

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

Системы могут реализовать переплетение на уровне исходного кода с помощью препроцессоров (поскольку C ++ изначально был реализован в CFront ), которым требуется доступ к исходным файлам программы. Однако четко определенная двоичная форма Java позволяет ткачам байт-кода работать с любой программой Java в форме файла .class. Ткачи байт-кода могут быть развернуты во время процесса сборки или, если модель плетения для каждого класса, во время загрузки класса. AspectJ начал с переплетения на уровне исходного кода в 2001 году, предоставил ткацкий механизм байт-кода для каждого класса в 2002 году и предложил расширенную поддержку времени загрузки после интеграции AspectWerkz в 2005 году.

Любое решение, объединяющее программы во время выполнения, должно предоставлять представления, которые должным образом разделяют их, чтобы поддерживать раздельную модель программиста. Поддержка байт-кода Java для нескольких исходных файлов позволяет любому отладчику пройти через правильно сплетенный файл .class в редакторе исходного кода. Однако некоторые сторонние декомпиляторы не могут обрабатывать сплетенный код, потому что они ожидают код, созданный Javac, а не все поддерживаемые формы байт-кода (см. Также § Критика ниже).

Ткачество во время развертывания предлагает другой подход. Это в основном подразумевает постобработку, но вместо исправления сгенерированного кода этот подход ткачества подклассифицирует существующие классы, так что модификации вводятся путем переопределения метода. Существующие классы остаются нетронутыми даже во время выполнения, и все существующие инструменты (отладчики, профилировщики и т. Д.) Могут использоваться во время разработки. Подобный подход уже зарекомендовал себя в реализации многих Java EE серверов приложений, таких как IBM «s WebSphere .

Терминология

Стандартная терминология, используемая в аспектно-ориентированном программировании, может включать:

Общие проблемы
Несмотря на то, что большинство классов в объектно-ориентированной модели будут выполнять одну конкретную функцию, они часто имеют общие вторичные требования с другими классами. Например, мы можем захотеть добавить ведение журнала к классам на уровне доступа к данным, а также к классам на уровне пользовательского интерфейса всякий раз, когда поток входит в метод или выходит из него. Дальнейшие проблемы могут быть связаны с безопасностью, такой как управление доступом или управление информационными потоками . Несмотря на то, что каждый класс имеет совершенно разные основные функции, код, необходимый для выполнения дополнительных функций, часто идентичен.
Совет
Это дополнительный код, который вы хотите применить к существующей модели. В нашем примере это код ведения журнала, который мы хотим применять всякий раз, когда поток входит в метод или выходит из него.
Pointcut
Это термин, обозначающий точку выполнения в приложении, к которой необходимо применить сквозное внимание. В нашем примере pointcut достигается, когда поток входит в метод, а другой pointcut достигается, когда поток выходит из метода.
Аспект
Комбинация pointcut и совета называется аспектом. В приведенном выше примере мы добавляем аспект ведения журнала в наше приложение, определяя pointcut и давая правильный совет.

Сравнение с другими парадигмами программирования

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

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

Хотя это может показаться несвязанным, при тестировании использование имитаторов или заглушек требует использования методов АОП, таких как советы и т. Д. Здесь взаимодействующие объекты предназначены для целей теста, сквозной проблемы. Таким образом, различные фреймворки Mock Object предоставляют эти функции. Например, процесс вызывает службу для получения суммы баланса. При тестировании процесса, откуда берется сумма, неважно, только то, что процесс использует баланс в соответствии с требованиями.

Проблемы с усыновлением

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

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

Критика

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

 5 INPUT X
10 PRINT 'Result is :'
15 PRINT X
20 COME FROM 10
25      X = X * X
30 RETURN

с фрагментом АОП с аналогичной семантикой:

main() {
    input x
    print(result(x))
}
input result(int x) { return x }
around(int x): call(result(int)) && args(x) {
    int temp = proceed(x)
    return temp * temp
}

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

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

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

Реализации

Следующие языки программирования реализовали АОП внутри языка или в виде внешней библиотеки:

Смотрите также

Примечания и ссылки

дальнейшее чтение

внешние ссылки