Aspektově orientované programování

Aspektově orientované programování (zkracováno na AOP, z anglického Aspect Oriented Programming) je programovací paradigma, které má za cíl zvýšit modularitu programu. Pokouší se rozdělit program na jasné části, které se mezi sebou co nejméně překrývají svou funkcionalitou. AOP se začalo hojně používat zejména v roce 2004.

Úvod

Většina programovacích paradigmat podporuje určitou úroveň seskupení a zapouzdření dat do samostatných, nezávislých subjektů. Některé však vzdorují této formě implementace a jsou nazývány průřezové problémy (crosscutting concerns), protože se nachází ve více částech programu. AOP má za cíl nahradit v kódu opakující se činnosti, vzorovým příkladem průřezového problému je logování, protože se týká každé jednotlivé logované části programu. Logování tedy prořezává všechny logované třídy, metody i procedury. Všechny implementace AOP mají nějaké průřezové výrazy, které zapouzdří danou činnost na konkrétním místě. Rozdíl mezi implementacemi spočívá v náročnosti, bezpečnosti a použitelnosti poskytnutých konstrukcí. AspectJ má řadu těchto výrazů a zapouzdřuje je ve speciální třídě, zvané aspekt. Aspekt může upravit chování základního kódu (neaspektové části programu) použitím advice (dodatečné chování) v různých joinpoints (body ve struktuře programu), zvaný pointcut (soubor joinpointů, pro které je spouštěna stejná advice). Aspekt také může dělat binárně kompatibilní strukturální změny jiných tříd, což je například přidání členů nebo rodičů.

Historie

Aspektově orientované programování má několik přímých předchůdců: reflexe a metaobjektové protokoly, objektové programování, filtry a adaptivní programování.

Tento koncept vymyslel Gregor Kiczales s kolegy v Xerox PARC. Stejný tým vyvinul i první a zatím nejpoužívanější aspektově orientovaný jazyk AspectJ.

Microsoft Transaction Server je považován za první hlavní použití AOP následovaný Enterprise Java Beans.

Typy aspektově orientovaného programování

AOP se dá rozdělit na statické a dynamické. Statické AOP poskytuje například AspectJ a dynamické Spring Framework.

Statické AOP

Statické AOP je rychlejší než dynamické, jelikož weaving (proces vkládání aspektů do aplikace) probíhá již při buildu aplikace, přibývá zde další krok, avšak kód AOP již při běhu aplikace nelze měnit. Potřebujeme-li tedy udělat jakoukoli změnu za běhu aplikace, jsme nuceni k opětovné kompilaci celé aplikace. Statické AOP se používá například v již zmiňovaném AspectJ.

Dynamické AOP

Dynamické AOP je sice oproti statickému AOP pomalejší, ale můžeme měnit kód zcela nezávisle na aplikaci. Změny v AOP tedy neznamenají nutnou opětovnou kompilaci celé aplikace. Je to způsobené tím, že u dynamických AOP probíhá weaving až při běhu aplikace. U různých implementací je toho dosaženo za pomoci různých technik, nejčastěji je však používáno proxy pro každý objekt, který využívá aspekty.

Motivace a základní koncepty

Aspekt je typicky rozptýlen jako kód, takže není zcela lehké ho pochopit a udržovat. Aspekt je rozptýlen na základě funkce (například logování) a je rozložen do několika nesouvisejících funkcí, které by mohly používat jeho funkce, případně ve zcela nesouvisejících systémech, různých zdrojových jazycích atd. To znamená, že ke změně logování může vyžadovat modifikaci všech dotyčných modulů. Aspekty nejsou “zamotané” pouze s hlavními funkcemi systému, ve kterém jsou vyjádřené, ale i mezi sebou navzájem. Například si představme bankovní aplikaci s koncepčně velmi jednoduchou metodou na převod částky z jednoho účtu na druhý:[1]

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

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

Nicméně, tato metoda pro převod peněz mezi účty je velice vzdálená reálným bankovním aplikacím, jelikož, kvůli bezpečnosti, musíme ověřit, zda má aktuální uživatel autorizaci k provedení této operace. Musíme také uzavřít tuto operaci do databázové transakce, abychom předešli nekonzistenci dat. Zjednodušená verze s těmito novými koncerny (problémy) by mohla vypadat nějak takto:

void transfer(Account fromAcc, Account toAcc, int amount, User user, Logger logger) 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.");
}

Kód již není tak jednoduchý a elegantní, jelikož jsme přidali různé další koncerny (problémy) k základní funkcionalitě (někdy zvané koncern obchodní logiky). Transakce, zabezpečení a logování jsou příklady průřezových problémů. Nyní si představme, co se stane, když najednou potřebujeme změnit (například) bezpečnostní schéma pro danou aplikaci. V současné verzi programu jsou operace týkající se zabezpečení “rozesety” v mnoha metodách, a tak by změna vyžadovala značné úsilí. AOP se snaží tento problém vyřešit tím, že programátor vyjádří průřezové problémy v samostatných modulech zvaných aspekty. Aspekty mohou obsahovat advice (kód spojený s určitými body v programu) a inter-type declarations (rozšíření deklarace tříd). Například bezpečnostní modul může obsahovat advice, který provádí bezpečnostní kontrolu před vstupem do bankovního účtu. Pointcut definuje dobu (join points), kdy je možné získat přístup k bankovnímu účtu a kód v těle advice určuje, jak je bezpečnostní kontrola implementována. To je způsob, jak mohou být kontrola a místo udržovány na jednom místě. Dobrý pointcut, také může předvídat pozdější programové změny, takže pokud jiný vývojář vytvoří novou metodu pro přístup k bankovnímu účtu, advice se bude vztahovat i na nové metody, při jejich provádění. Takže takto se pro výše uvedený příklad provádí záznam v aspektu:

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.
}

Join point modely (JPM)

Způsob spolupráce aspektu s programem je definován v join point modelu (z anglického join point model). JPM definuje tři věci: Join points - místa, do kterých je možné do kódu vložit logiku pomocí AOP Advice - kód, který se spouští v join pointu, může se spouštět před (before) i za (after) join pointem Pointcut - je soubor join pointů, ve kterých je spuštěna stejná advice

Srovnání s jinými programovacími paradigmaty

Aspekty vycházejí z objektově orientovaného programování (OOP). AOP jazyky nabízí podobné funkce jako metaobject protokoly. Aspekty úzce souvisí s programovacími koncepty jako subjekty, mixiny a delegace. Již od roku 1970 vývojáři používali formy odposlechu (interception) a záplatování (dispatch-patching), které se podobají některým ze způsobů implementace pro AOP, ale nikdy nebyly označovány jako cross cutting specifikace a sepsány na jednom místě. Návrháři zvažovali i jiné způsoby, jak dosáhnout odděleného kódu, jako jsou například dílčí typy (partial types) v C#, těmto přístupům však chybí kvantifikační mechanismus, který umožňuje propojení několika join pointů s jednou deklarací.

Reference

  1. Poznámka: Pro ukázky v tomto článku je použita syntaxe jazyka Java.