В компютърните науки аспектно-ориентираното програмиране (АОП) е парадигма на програмиране, която има за цел да повиши модулността, позволявайки изолирането на пресичащи функционални дялове. Това се постига чрез добавяне на допълнително поведение на съществуващия код, наречено ”адвайс” (), без промяна на самия код, като вместо това отделно се определя кой код ще бъде променен посредством задаване на пресечна точка (), като например „отрази в регистъра всички извиквания на функции, в които името на функцията започва със „set“. Това позволява към програмата да се добавят поведения, които не са централни за бизнес логиката (например записване в регистър), без да се повлиява на функционалността на основния код. АОП създава основа за разработка на аспектно-ориентиран софтуер.
АОП включва програмни методи и инструменти, които подпомагат разделянето на функционалните дялове в модули на ниво сорс код, докато „разработка на аспектно-ориентиран софтуер“ се отнася за цялата инженерна дисциплина.
Аспектно-ориентираното програмиране изисква разбиване на програмната логика на отделни части (т.нар. функционални дялове, обособени функционални елементи от кода). Почти всички програмни парадигми предоставят някакво ниво на групиране и капсулация на функционални дялове в отделни, независими елементи посредством абстракции (напр. функции, процедури, модули, класове, методи), които могат да бъдат използвани за имплементация, превръщане в абстракция и съставяне на тези функционални дялове. Някои функционални дялове „пресичат“ няколко абстракции в програмата и не се подчиняват на тези форми на имплементация. Тези функционални дялове се наричат пресичащи или хоризонтални функционални дялове.
Записването в регистър е пример за пресичащ функционален дял, тъй като стратегията за регистриране на данни по необходимост влияе на всяка отразявана в регистъра част на системата. По тази причина записването в регистър пресича всички отразявани в регистър класове и методи.
Всички имплементации на АОП имат пресичащи изрази, които капсулират всеки функционален дял на едно място. Разликата между имплементациите се крие в мощността, сигурността и използваемостта на предоставените конструкции. Например, интерсепторите (), определящи методите, които да въздействат на изпълнението на програмата, представляват ограничена форма на пресичане без значителна поддръжка на типова безопасност или дебъгване. AspectJ предлага множество такива изрази, които капсулира в специален клас, наречен аспект. Аспектът може да промени поведението на основния код (неаспектната част на програмата) посредством прилагане на адвайс (допълнително поведение) в различни съединени точки (), определени в квантификатор (quantification) или заявка, наречена пресечна точка (която проверява дали дадена съединена точка отговаря). Един аспект може също така да извърши бинарно-съвместими структурни промени в други класове, като добавяне на членове или родители.
История AOП има няколко преки предшественици А1 и А2: рефлексия и метаобектни протоколи, предметно-ориентирано програмиране, Композиционни Филтри и Адаптивно програмиране „Adaptive Object Oriented Programming: The Demeter Approach with Propagation Patterns“ Karl Liebherr 1996 ISBN 0-534-94602-X presents a well-worked version of essentially the same thing (Lieberherr subsequently recognized this and reframed his approach)..
Грегор Кишалес и колегите му от Xerox PARC разработват първоначалната концепция за AOP, на базата, на която създават разширението AspectJ AOP за Джава. Изследователският екип на IBM преследва инструменталния подход пред този с езиков дизайн и през 2001 година предлага Hyper J и Concern Manipulation Environment, които така и не успяват да придобият широка употреба. Примерите в тази статия използват AspectJ, тъй като това е най-широко разпространения език за AOP. Microsoft Transaction Server е считан за първото голямо приложение на AOP последвано от Enterprise JavaBeans.
Мотивация и основни концепции Обикновено един аспект е разпръснат или заплетен като код, което го прави труден за разбиране и поддръжка. Разпръснат въз основа на функцията (например разцепен), която се простира из множество отделни функции, които може да използват функцията му, в изцяло несвързани системи, различни изходни езици и други. Това означава, че за да се промени разцепеността му (logging) ще трябва да се променят всички засегнати модули. Аспектите стават заплетени не само с главната функция на системите, в които са изразени, но също и един с друг. Това означава, че при промяната на един трябва да се погрижим за цялостното разбиране на всички заплетени такива. В противен случай трябва да имаме някакви средства, с помощта на които ефекта предизвикан от промяната може да бъде заключен.
Например, да вземем едно банково приложение с концептуално прост метод за прехвърляне на сума от една сметка в друга:Бележка: Примерите в тази статия са със синтаксис подобен на Джава. 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."); } В този пример други интереси станаха заплетени с основната функционалност (понякога ги наричат функционалност на бизнес логиката). Трансакции, сигурност, и логване – всички илюстрират напречно вкараната функционалност.
Какво би се случило ако случайно трябва да сменим например функционалността засягаща сигурността на приложението ни. В настоящата версия на програмата, всички операции свързани със сигурността са разцепени из многобройните методи и подобна промяна би изискала огромни усилия. AOP опитва да разреши този проблем, като позволява на програмиста да изрази напречно вкарана функционалност чрез самостоятелни модули, наричани аспекти. Аспектите могат да съдържат адвайс (присъединен код за специфициране на определени точки в програмата) и междутипови декларации (структурни членове, добавени към други класове). Например, модул за сигурността може да включи адвайс, който изпълнява проверка на сигурността преди достъпа до банковата сметка. Пойнткътът определя колко пъти (колко съединени точки), някой може да получи достъп до банкова сметка, а кодът в тялото на адвайса определя как сигурността да бъде приложена. По този начин и двете – проверката и местата могат да се поддържат на едно място. Освен това един добър пойнткът може да предвиди по-късни промени в програмата, така че ако друг разработчик създаде нов метод за достъп до банковата сметка, адвайсът ще се приложи и на новия метод, когато той се изпълнява.
Така че, за горния пример прилагаме разцепването в аспект: 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.");
// Друга напречно вкарана функционалност. } АОП може да се възприема като инструмент за дебъгване или като инструмент на ниво потребител. Адвайс би трябвало да бъде съхранен за случаите, когато не може да се накара функцията да се промени (потребителско ниво) или не се иска да се променя функция на код в продукция (дебъгване).
Модели на съединени точки Компонентът адвайс на аспектно – ориентирания език дефинира модели на съединени точки (МСТ).
МСТ определят три неща: Кога адвайсът може да бъде пуснат. Наречени са съединени точки, защото те са точки на работеща програма, където допълнително поведение може да бъде полезно присъединено. За да бъде полезна, съединената точка трябва да може да бъде адресирана и разбрана от всеки обикновен програмист. Трябва също да бъде стабилна при незначителни промени в програмата, за да може всеки отделен аспект да бъде стабилен при такива промени. Много АОП имплементации поддържат метод за изпълнение и полеви препратки във вид на съединени точки. Пойнткът се нарича начина за определяне (или количествено определяне) на съединени точки. Пойнткътите определят дали дадена съединени точка съвпада. Най-полезните пойнткътите програмни езици използват подобен синтаксис на базовия език за програмиране (например използва Джава сигнатури) и позволяват повторна употреба чрез наименуване и комбиниране. Средство да се зададе на кода да тръгне по съединени точка. AspectJ нарича това адвайс и може да го пусне преди, след и около съединени точка. Някои имплементации също поддържат неща като дефиниране на метод по отношение на друг клас.
Моделите на съединени точки могат да бъдат сравнени на базата на изложените съединени точки, по какъв начин те са специфицирани, позволените им операции, и структурните подобрения, които могат да бъдат изразени.
Модел на съединени точки на AspectJ Главна статия: AspectJ Съединените точки в AspectJ включват извикването на метод или конструкция или изпълнението, инициализирането на клас или обект, достъпа до поле за четене и писане, обработка на изключения и т.н. Те не включват цикли, супер извиквания, хвърляне на клаузи, множество изявления и др. Съединените точки са определят чрез комбинирането на обозначители на примитивни съединени точки (ОПСТ). „Типова“ ОПСТ съвпада със съединена точка от определен тип (например изпълнението на метод) и обикновено взима за вход сигнатура като при Джава. Подобна пойнткът би изглеждала така: execution(*set*(*)) Този пойнткът съвпада с метод execution на съединена точка, ако името на метода започва със „set“ и има точно един аргумент от какъвто и да е тип в него. „Динамични“ ОПСТ проверяват рънтайм видове и обвързват променливи. Например: this(Point) Този пойнткът съвпада, когато обектът, който се изпълнява е инстанция на класа Point. Имайте предвид, че некласифицирано име на един клас може да бъде използвано и чрез нормалния вид търсене на Джава. „Обхват“ ОПСТ ограничава лексикално обхвата на съединените точки. Например: within(com.company.*) Този пойнткът съвпада с всяка съединена точка от всеки вид в пакета com.company. * е форма на заместващ символ, който може да бъде използван за съчетаване на много неща с еднаква сигнатура.
Пойнткътите могат да бъдат съставяни и именувани за повторно използване. pointcut set() : execution(* set*(*)) && this(Point) && within(com.company.*); Този пойнткът съвпада със съединена точка на метод execution, ако името на метода започва със „set“ и this е инстанция на тип Point в пакет com.company. Mоже да бъде реферирана като се използва името „set()“. Адвайс-а определя да пусне (преди, след или около) съединена точка (определена чрез пойнткът) определен код (специфициран като код в метод). АОП рънтайм извиква Адвайс-а автоматично, когато пойнткъта съвпада със съединената точка. Например:
after() : set() { Display.update();
Това на практика уточнява: "ако set() пойнткът съвпадне със съединената точка стартирай кода Display.update(), след като съединената точка се изпълни. "