Aspektově orientované programování - Aspect-oriented programming

V oblasti výpočetní techniky je aspektově orientované programování ( AOP ) paradigmatem programování , jehož cílem je zvýšit modularitu tím, že umožní oddělení průřezových problémů . Činí to tak, že ke stávajícímu kódu ( rada ) přidá další chování, aniž by upravoval samotný kód, místo toho samostatně určil, který kód je upraven pomocí specifikace „ pointcut “, například „zaznamenávat všechna volání funkcí, když název funkce začíná„ set “. To umožňuje, aby chování, která nejsou zásadní pro obchodní logiku (například protokolování), byla přidána do programu, aniž by došlo k přeplnění jádra kódu funkcemi.

AOP zahrnuje programovací metody a nástroje, které podporují modularizaci problémů na úrovni zdrojového kódu, zatímco aspektově orientovaný vývoj softwaru se týká celé inženýrské disciplíny.

Aspektově orientované programování zahrnuje rozdělení logiky programu na odlišné části (takzvané starosti , soudržné oblasti funkčnosti). Téměř všechna programovací paradigmata podporují určitou úroveň seskupování a zapouzdření problémů do samostatných nezávislých entit poskytováním abstrakcí (např. Funkcí, procedur, modulů, tříd, metod), které lze použít k implementaci, abstrakci a skládání těchto problémů. Některé obavy „procházejí napříč“ více abstrakcemi v programu a vzpírají se těmto formám implementace. Tyto obavy se nazývají průřezové nebo horizontální obavy.

Protokolování je příkladem obav z průřezů, protože strategie protokolování nutně ovlivňuje každou přihlášenou část systému. Protokolování tím překračuje všechny protokolované třídy a metody.

Všechny implementace AOP mají některé příčné výrazy, které zapouzdřují každý problém na jednom místě. Rozdíl mezi implementacemi spočívá v síle, bezpečnosti a použitelnosti poskytovaných konstrukcí. Například interceptory, které specifikují metody pro vyjádření omezené formy křížení, bez velké podpory pro bezpečnost typu nebo ladění. AspectJ má řadu takových výrazů a zapouzdřuje je ve speciální třídě, aspektu . Aspekt může například změnit chování základního kódu (část programu bez aspektů) použitím rady (dodatečného chování) v různých spojovacích bodech (bodech v programu) zadaných v kvantifikaci nebo dotazu nazývaném pointcut ( který detekuje, zda se daný bod spojení shoduje). Aspekt může také provádět binární kompatibilní strukturální změny v jiných třídách, jako je přidání členů nebo rodičů.

Dějiny

AOP má několik přímých předchůdců A1 a A2: reflexní a metaobjektové protokoly , předmětově orientované programování , kompoziční filtry a adaptivní programování.

Gregor Kiczales a kolegové ze společnosti Xerox PARC vyvinuli explicitní koncept AOP a následovali jej pomocí rozšíření AspectJ AOP do Javy. Výzkumný tým IBM sledoval nástrojový přístup nad přístupem k navrhování jazyků a v roce 2001 navrhl Hyper/J a Concern Manipulation Environment , u nichž nebylo široké využití.

Příklady v tomto článku používají AspectJ.

Microsoft Transaction Server je považován za první hlavní aplikace AOP následuje Enterprise JavaBeans .

Motivace a základní pojmy

Aspekt je obvykle rozptýlen nebo zamotán jako kód, což ztěžuje jeho pochopení a údržbu. Je rozptýlena v důsledku toho, že funkce (například protokolování) je rozložena na řadu nesouvisejících funkcí, které by mohly používat její funkci, případně ve zcela nesouvisejících systémech, různých zdrojových jazycích atd. To znamená, že ke změně protokolování může být nutné upravit všechny ovlivněné moduly . Aspekty se pletou nejen s hlavní funkcí systémů, ve kterých jsou vyjádřeny, ale také navzájem. To znamená, že změna jedné starosti znamená porozumět všem zamotaným problémům nebo mít nějaké prostředky, pomocí kterých lze odvodit účinek změn.

Zvažte například bankovní aplikaci s koncepčně velmi jednoduchým způsobem převodu částky z jednoho účtu na druhý:

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

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

Tato metoda přenosu však přehlíží určité úvahy, které by nasazená aplikace vyžadovala: postrádá bezpečnostní kontroly k ověření, že aktuální uživatel má oprávnění k provedení této operace; databáze transakce by měla zapouzdřit provoz, aby se zabránilo náhodné ztrátě dat; pro diagnostiku by měla být operace zaznamenána do systémového protokolu atd.

Verze se všemi těmito novými obavami by například mohla vypadat nějak takto:

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.");
}

V tomto případě se další zájmy zamotaly se základní funkčností (někdy se jí říká obchodní logika ). Transakce, zabezpečení a protokolování jsou příkladem průřezových problémů .

Nyní zvažte, co se stane, pokud najednou potřebujeme změnit (například) bezpečnostní aspekty aplikace. V aktuální verzi programu se operace související se zabezpečením objevují roztroušeně v mnoha metodách a taková změna by vyžadovala velké úsilí.

AOP se pokouší vyřešit tento problém tím, že umožňuje programátorovi vyjádřit průřezové obavy v samostatných modulech nazývaných aspekty . Aspekty mohou obsahovat rady (kód spojený se zadanými body v programu) a deklarace mezi typy (konstrukční prvky přidány do jiných tříd). Bezpečnostní modul může například obsahovat rady, které provádějí bezpečnostní kontrolu před přístupem k bankovnímu účtu. Pointcut definuje časy ( připojit bodů ), když jeden přístup bankovního účtu a kód v těle definuje doporučení, jak je implementována kontrola zabezpečení. Tímto způsobem lze šek i místa udržovat na jednom místě. Dobrý bodový bod navíc může předvídat pozdější změny programu, takže pokud jiný vývojář vytvoří novou metodu přístupu k bankovnímu účtu, rada se na novou metodu použije při jejím spuštění.

Takže pro výše uvedený příklad implementace protokolování 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.
}

AOP lze považovat za nástroj pro ladění nebo jako nástroj na úrovni uživatele. Poradenství by mělo být vyhrazeno pro případy, kdy nemůžete funkci změnit (úroveň uživatele) nebo nechcete funkci změnit v produkčním kódu (ladění).

Připojte se k bodovým modelům

Komponenta aspektově orientovaného jazyka související s radami definuje model bodu spojení (JPM). JPM definuje tři věci:

  1. Když rada může běžet. Říká se jim body spojení, protože jsou to body v běžícím programu, kde lze užitečně spojit další chování. Spojovací bod musí být adresovatelný a srozumitelný běžnému programátorovi, aby byl užitečný. Mělo by být také stabilní napříč nepodstatnými změnami programu, aby byl aspekt stabilní i při takových změnách. Mnoho implementací AOP podporuje provádění metod a odkazy na pole jako body spojení.
  2. Způsob, jak určit (nebo kvantifikovat ) spojovací body, nazývaný bodové řezy . Bodové řezy určují, zda se daný bod spojení shoduje. Nejužitečnější jazyky pointcutů používají syntaxi jako základní jazyk (například AspectJ používá podpisy Java) a umožňují opětovné použití pojmenováním a kombinováním.
  3. Prostředek k zadání kódu, který se má spustit v bodě spojení. AspectJ tuto radu nazývá a může ji spustit před, po a kolem spojovacích bodů. Některé implementace také podporují věci, jako je definování metody v aspektu v jiné třídě.

Modely spojovacích bodů lze porovnávat na základě exponovaných spojovacích bodů, způsobu zadávání spojovacích bodů, operací povolených ve spojovacích bodech a strukturálních vylepšení, která lze vyjádřit.

AspectJ's join-point model

  • Spojovací body v AspectJ zahrnují volání nebo spuštění metody nebo konstruktoru, inicializaci třídy nebo objektu, přístup pro čtení a zápis do pole, obsluhy výjimek atd. Nezahrnují smyčky, super volání, klauzule vrhání, více příkazů atd.
  • Bodové řezy jsou určeny kombinací primitivních označovačů bodů (PCD).

    „Kinded“ PCD se shodují s konkrétním druhem bodu spojení (např. Provádění metody) a obvykle berou jako vstup podpis podobný Javě. Jeden takový pointcut vypadá takto:

     execution(* set*(*))
    

    Tento bod se shoduje se spojovacím bodem spuštění metody, pokud název metody začíná „ set“ a existuje přesně jeden argument jakéhokoli typu.

    „Dynamické“ PCD kontrolují typy runtime a váží proměnné. Například,

      this(Point)
    

    Tento bod odpovídá, když je aktuálně provádějící objekt instancí třídy Point. Nekvalifikovaný název třídy lze použít pomocí normálního vyhledávání jazyka Java.

    PCD „Scope“ omezují lexikální rozsah spojovacího bodu. Například:

     within(com.company.*)
    

    Tento bod odpovídá jakémukoli bodu spojení v jakémkoli typu v com.companybalíčku. Je *jednou z forem zástupných znaků, které lze použít k přiřazení mnoha věcí k jednomu podpisu.

    Bodové řezy lze skládat a pojmenovávat pro opětovné použití. Například:

     pointcut set() : execution(* set*(*) ) && this(Point) && within(com.company.*);
    
    Tento bod se shoduje s bodem spuštění provádění metody, pokud název metody začíná „ set“ a thisje instancí typu Pointv com.companybalíčku. Lze jej označit názvem „ set()“.
  • Poradenství určuje spuštění v (před, po nebo kolem) bodu spojení (určeném bodem) určitým kódem (zadaným jako kód v metodě). Runtime AOP automaticky vyvolá Advice, když se bodový bod shoduje s bodem spojení. Například: after (): set () {Display.update (); } Toto ve skutečnosti určuje: „Pokud se set()bod shoduje s bodem spojení, spusťte kód Display.update()po dokončení bodu spojení.“

Další potenciální modely spojovacích bodů

Existují i ​​jiné druhy JPM. Všechny poradenské jazyky lze definovat z hlediska jejich JPM. Například hypotetický aspektový jazyk pro UML může mít následující JPM:

  • Spojovací body jsou všechny prvky modelu.
  • Bodové řezy jsou booleovský výraz kombinující prvky modelu.
  • Prostředky vlivu v těchto bodech jsou vizualizací všech odpovídajících spojovacích bodů.

Prohlášení mezi typy

Intertypová prohlášení poskytují způsob, jak vyjádřit obavy z průřezů ovlivňující strukturu modulů. Také známé jako otevřené třídy a rozšiřující metody , to umožňuje programátorům deklarovat na jednom místě členy nebo rodiče jiné třídy, obvykle za účelem kombinace veškerého kódu souvisejícího s obavou v jednom aspektu. Pokud například programátor implementoval obavu aktualizace příčného zobrazení pomocí návštěvníků místo toho, dekódování mezi typy pomocí vzoru návštěvníka může v AspectJ vypadat takto:

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

Tento fragment kódu přidá acceptVisitormetodu do Pointtřídy.

Je požadováno, aby všechny strukturální doplňky byly kompatibilní s původní třídou, aby klienti stávající třídy pokračovali v provozu, pokud implementace AOP nemůže očekávat, že bude kdykoli ovládat všechny klienty.

Implementace

Programy AOP mohou ovlivnit jiné programy dvěma různými způsoby, v závislosti na základních jazycích a prostředích:

  1. je vytvořen kombinovaný program, platný v původním jazyce a nerozeznatelný od běžného programu ke konečnému tlumočníkovi
  2. konečný tlumočník nebo prostředí je aktualizováno, aby porozumělo a implementovalo funkce AOP.

Obtížnost změny prostředí znamená, že většina implementací produkuje kompatibilní kombinované programy prostřednictvím typu transformace programu známého jako tkaní . Tkáč aspektů čte kód orientovaný na aspekty a generuje příslušný objektově orientovaný kód s integrovanými aspekty. Stejný jazyk AOP lze implementovat prostřednictvím různých metod tkaní, takže sémantika jazyka by nikdy neměla být chápána z hlediska implementace tkaní. Použitá metoda kombinace ovlivňuje pouze rychlost implementace a její snadné nasazení.

Systémy mohou implementovat tkaní na úrovni zdrojů pomocí preprocesorů (jak byl C ++ původně implementován v CFront ), které vyžadují přístup ke zdrojovým souborům programu. Dobře definovaná binární forma Javy však umožňuje tkalcům bajtových kódů pracovat s jakýmkoli programem Java ve formátu souboru .class. Bytecode weavers lze nasadit během procesu sestavení nebo, pokud je model vazby pro každou třídu, během načítání třídy. AspectJ začal s tkaním na úrovni zdrojů v roce 2001, v roce 2002 dodal tkadlec bytecode pro každou třídu a po integraci AspectWerkz v roce 2005 nabídl pokročilou podporu doby načítání .

Každé řešení, které kombinuje programy za běhu, musí poskytovat pohledy, které je správně oddělují, aby se zachoval segregovaný model programátora. Podpora bajtkódu Java pro více zdrojových souborů umožňuje každému ladicímu programu procházet správně tkaný soubor .class ve zdrojovém editoru. Některé dekompilátory třetích stran však nedokáží zpracovat tkaný kód, protože očekávají kód vytvořený Javacem, nikoli všechny podporované formy bytecode (viz také § Kritika , níže).

Doba tkaní při nasazení nabízí další přístup. To v zásadě znamená následné zpracování, ale namísto opravování generovaného kódu tento přístup tkaní podtřídy stávajících tříd, takže úpravy jsou zavedeny přepsáním metody. Stávající třídy zůstávají nedotčené, a to i za běhu, a během vývoje lze použít všechny existující nástroje (debuggery, profilery atd.). Podobný přístup se již osvědčila při realizaci mnoha Java EE aplikačních serverů, jako je IBM ‚s WebSphere .

Terminologie

Standardní terminologie používaná v Aspectově orientovaném programování může zahrnovat:

Průřezové starosti
Přestože většina tříd v modelu OO bude vykonávat jednu konkrétní funkci, často sdílejí společné, sekundární požadavky s jinými třídami. Můžeme například chtít přidat protokolování do tříd v rámci vrstvy přístupu k datům a také do tříd ve vrstvě uživatelského rozhraní, kdykoli vlákno zadá nebo opustí metodu. Další obavy mohou souviset s bezpečností, jako je řízení přístupu nebo řízení toku informací . Přestože každá třída má velmi odlišnou primární funkci, kód potřebný k provedení sekundární funkce je často identický.
Rada
Toto je další kód, který chcete použít na svůj stávající model. V našem příkladu se jedná o kód protokolování, který chceme použít vždy, když vlákno zadá nebo opustí metodu.
Pointcut
Toto je termín určený k bodu provedení v aplikaci, ve které je třeba uplatnit průřezové zájmy. V našem příkladu se dosáhne bodového řezu, když vlákno zadá metodu, a dalšího bodu se dosáhne, když vlákno opustí metodu.
Aspekt
Kombinace pointcutu a rady se označuje jako aspekt. Ve výše uvedeném příkladu přidáme do naší aplikace aspekt protokolování definováním bodového řezu a poskytnutím správných rad.

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

Aspekty vzešly z objektově orientovaného programování a výpočetní reflexe . Jazyky AOP mají podobnou funkci, ale jsou omezenější než protokoly metaobjektů . Aspekty úzce souvisí s koncepty programování, jako jsou předměty , mixiny a delegování . Mezi další způsoby využití paradigmat programování orientovaných na aspekty patří kompoziční filtry a přístup hyperslices . Minimálně od 70. let minulého století vývojáři používají formy zachycování a záplatování dispatch, které se podobají některým implementačním metodám pro AOP, ale nikdy neměly sémantiku, kterou specifikace křížení poskytují, napsanou na jednom místě.

Designéři zvažovali alternativní způsoby, jak dosáhnout oddělení kódu, jako jsou částečné typy C# , ale takové přístupy postrádají kvantifikační mechanismus, který umožňuje dosáhnout několika spojovacích bodů kódu jedním deklarativním příkazem.

Ačkoli se to může zdát nesouvisející, při testování použití falešných nebo zakázaných inzerátů vyžaduje použití technik AOP, jako jsou rady atd. Zde jsou spolupracující objekty pro účely testu průřezovým problémem. Tyto funkce tedy poskytují různé rámce Mock Object. Proces například vyvolá službu, aby získal částku zůstatku. V testu procesu, kde částka pochází, není důležité, pouze to, že proces používá zůstatek podle požadavků.

Problémy s adopcí

Programátoři musí být schopni číst kód a porozumět tomu, co se děje, aby se předešlo chybám. I se správným vzděláním může být pochopení problémů s průřezy obtížné bez řádné podpory pro vizualizaci statické struktury i dynamického toku programu. Počínaje rokem 2002 začala společnost AspectJ poskytovat plug-iny IDE, které podporovaly vizualizaci obav z průřezů. Tyto funkce, stejně jako pomoc s kódem aspektu a refaktoring jsou nyní běžné.

Pokud programátor udělá logickou chybu při vyjadřování příčných řezů, může to vzhledem k síle AOP vést k rozsáhlému selhání programu. Naopak jiný programátor může změnit body spojení v programu - např. Přejmenováním nebo přesunem metod - způsoby, které autor aspektů nepředpokládal, s nepředvídanými důsledky . Jednou z výhod problémů s modularizací příčných řezů je umožnění jednomu programátoru snadno ovlivnit celý systém; v důsledku toho se takové problémy projevují jako konflikt ohledně odpovědnosti mezi dvěma nebo více vývojáři za dané selhání. Řešení těchto problémů však může být za přítomnosti AOP mnohem jednodušší, protože je třeba změnit pouze aspekt, zatímco odpovídající problémy bez AOP lze mnohem více rozložit.

Kritika

Nejzákladnější kritikou účinku AOP je to, že tok řízení je zastřený a že je nejen horší než tolik opovrhovaný GOTO , ale je ve skutečnosti velmi podobný vtipu COME FROM . Zapomenutí aplikace , která je zásadní pro mnoho definic AOP (kód v otázce nemá žádný náznak toho, že bude platit radu, která je určena místo v pointcut), znamená, že rada není vidět, na rozdíl od explicitní volání metody. Porovnejte například program COME FROM:

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

s fragmentem AOP s analogickou sémantikou:

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
}

Bodový řez může skutečně záviset na podmínkách běhu, a proto nemusí být staticky deterministický. To lze zmírnit, ale ne vyřešit pomocí statické analýzy a podpory IDE, která ukazuje, které rady se potenciálně shodují.

Obecná kritika je, že AOP má za cíl zlepšit „jak modularitu, tak strukturu kódu“, ale někteří tvrdí, že místo toho podkopává tyto cíle a brání „nezávislému vývoji a srozumitelnosti programů“. Konkrétně kvantifikace pomocí bodových řezů narušuje modularitu: „člověk musí mít obecně znalosti celého programu, aby mohl uvažovat o dynamickém provádění programu orientovaného na aspekty“. Kromě toho, zatímco jeho cíle (modularizující průřezové záležitosti) jsou dobře známy, jeho skutečná definice je nejasná a není jasně odlišena od jiných zavedených technik. Průřezové záležitosti se potenciálně navzájem kříží a vyžadují určitý mechanismus řešení, jako je objednávání. Aspekty se skutečně mohou vztahovat na sebe, což vede k problémům, jako je lhářský paradox .

Technická kritika zahrnuje, že kvantifikace pointcutů (definujících, kde jsou rady prováděny) je „extrémně citlivá na změny v programu“, což je známé jako křehký problém pointcutů . Problémy s bodovými řezy jsou považovány za neřešitelné: pokud někdo nahradí kvantifikaci bodových řezů explicitními anotacemi, získá místo toho programování orientované na atributy , což je jednoduše explicitní volání podprogramu a trpí stejným problémem rozptylu, který měl AOP vyřešit.

Implementace

Následující programovací jazyky implementovaly AOP v rámci jazyka nebo jako externí knihovnu:

Viz také

Poznámky a reference

Další čtení

externí odkazy