Programmazione orientata agli aspetti

La programmazione orientata agli aspetti ( AOP ) è un paradigma di programmazione per la programmazione orientata agli oggetti al fine di utilizzare funzionalità generiche in diverse classi ( preoccupazione trasversale ). Gli aspetti logici di un programma applicativo sono separati dalla logica di business effettiva. Esempi tipici di applicazioni sono la gestione delle transazioni , la verificabilità e il comportamento di registrazione .

Il concetto di AOP è stato sviluppato da Gregor Kiczales e dal suo team di Xerox PARC . Nel 2001 è stato presentato anche il primo linguaggio AOP AspectJ .

motivazione

In linea di principio, il software deve soddisfare determinati compiti / requisiti. Questi requisiti possono essere approssimativamente suddivisi in due aree:

  1. Le cosiddette preoccupazioni a livello di core (riguardano il "core" logico dell'applicazione) o requisiti funzionali. Questi sono requisiti per il software che di solito possono essere facilmente incapsulati in singole funzioni. Un esempio potrebbe essere il calcolo di un valore.
  2. Le preoccupazioni a livello di sistema (interessano l'intero sistema) o le condizioni limite tecniche. Questi requisiti non possono essere semplicemente incapsulati perché devono essere implementati in molti luoghi. Un primo esempio di ciò è la registrazione , la registrazione del flusso del programma nei file di registro. Il richiamo del logger non è necessario per la funzionalità effettiva, ma deve comunque essere integrato nel codice sorgente. Un altro esempio potrebbe essere la transazione degli accessi a una risorsa come B. un database.

Queste due parti sono intrecciate. Le preoccupazioni a livello di base possono essere indicate come componenti e le preoccupazioni a livello di sistema sono gli aspetti. Le preoccupazioni a livello di core vengono solitamente implementate come moduli o oggetti . Prima della programmazione orientata agli aspetti, non esisteva una soluzione elegante per gli aspetti.

Il problema dei requisiti intrecciati viene anche definito problemi trasversali , perché "tagliano" tutti i livelli logici del sistema. AOP è lo strumento per separare anche fisicamente le preoccupazioni logicamente indipendenti. L'obiettivo è generare codice più manutenibile e riutilizzabile .

In questo contesto, tuttavia, le preoccupazioni a livello di sistema non devono essere equiparate a questioni puramente tecniche. Problemi tecnici come B. l'implementazione di un sistema di autorizzazione professionale separato per gli utenti può certamente avere un carattere di aspetto.

sfondo

Nella storia dello sviluppo dei linguaggi di programmazione , nuovi concetti di programmazione e linguaggi di alto livello che li implementano sono stati sviluppati più e più volte, a partire dalla programmazione in assembler , che ha sostituito la programmazione diretta in codice binario , attraverso la programmazione procedurale e funzionale, ai linguaggi a oggetti odierni. Lo scopo è quello di rendere il lavoro degli sviluppatori più facile e quindi di ottenere una migliore efficienza nello sviluppo del software. Uno dei principi di base che è stato applicato a tutti i nuovi concetti di programmazione è stato quello dell'incapsulamento delle funzionalità.

L'abilitazione della funzionalità estesa non è l'obiettivo di questo sviluppo, perché ogni linguaggio di alto livello viene mappato al codice macchina dopo la compilazione.

In particolare, l'incapsulamento della funzionalità facilita lo sviluppo del software aumentando la manutenibilità e la riusabilità dei codici di programma esistenti. Poiché il software diventa sempre più complesso ed esteso nel tempo, rendendo lo sviluppo più lungo e costoso, questi due obiettivi sono diventati sempre più importanti e sono ora elementi centrali nello sviluppo di nuovi concetti e linguaggi di programmazione.

L'AOP (programmazione orientata agli aspetti o programmazione orientata agli aspetti) è un concetto di programmazione che affronta il problema delle cosiddette questioni trasversali .

Lo sviluppo e la manutenzione convenienti e tempestivi di software di alta qualità è l'obiettivo principale dell'ingegneria del software . Per raggiungere questo obiettivo è necessario un software il più modulare possibile con la minore complessità possibile dei moduli.

In un sistema convenzionale, che include anche gli approcci orientati agli oggetti, le funzionalità di base, le preoccupazioni principali dell'inglese , possono essere separate ordinatamente in moduli secondo le regole dell'arte. Tuttavia, ci sono preoccupazioni (preoccupazioni, preoccupazioni, requisiti) come la gestione degli errori, le prestazioni e la sicurezza in ogni sistema che intersecano le funzionalità principali e quindi non possono essere chiaramente assegnate a un modulo software. Di conseguenza, frammenti di tali problemi trasversali (funzionalità trasversali fondamentali (requisiti generali) - mancanza di coesione ) non vengono assegnati e sono sparsi in tutto il codice in modo non incapsulato. Queste funzionalità fondamentali trasversali impediscono la corretta modularizzazione nei sistemi software convenzionali e compromettono la manutenzione, la comprensibilità, la riutilizzabilità e (tracciabilità). Nei linguaggi di programmazione convenzionali , la scomposizione del sistema è responsabile di ciò , che consente solo una dimensione: l'elenco delle funzioni. Questo fenomeno è anche chiamato decomposizione dominante . In altre parole, un problema naturalmente multidimensionale deve essere risolto unidimensionale.

Derivazione da precedenti paradigmi

Nella programmazione procedurale, l'esecuzione del codice è relativamente lineare. Seguendo i simboli, ogni singola fase del programma può essere tracciata direttamente, anche guardando un sottosistema. Esempio in C :

void function (void * c)
{
    Component_repaint(c);
}

La funzione Component_repaint()è chiara. Indipendentemente dal fatto che il puntatore abbia cil tipo dinamico Componento un tipo derivato, viene sempre Component_repaint(Component*)chiamata la stessa funzione ( tipizzazione statica ).

Nella programmazione orientata agli oggetti, la tracciabilità è ridotta dal polimorfismo . Esempio in Java :

void function (Component c)
{
    c.repaint();
}

Non è chiaro quale repaint()metodo venga eseguito, dipende dal tipo effettivo di oggetto a cui fa criferimento. Un Componenttipo derivato da potrebbe repaint()definire il proprio metodo sovrascrivendo il metodo Component ereditato da repaint().

Nella programmazione orientata agli aspetti, la tracciabilità viene ulteriormente ridotta attraverso l'uso di pointcuts . Un pointcut contiene il codice da eseguire per un punto di unione , un punto di unione è un evento di chiamata definito con precisione. Qui è possibile attivare i consigli per quasi tutti i punti della catena di chiamate . In casi estremi è persino possibile impedire il richiamo dei metodi function()o di repaint()se stessi. Si potrebbe immaginare un aspetto che definisce un consiglio per il punto di unione “chiama il metodo function()” che proibisce esplicitamente l'ulteriore elaborazione di questa chiamata di funzione. Si potrebbe anche specificare in consiglio che un altro metodo dovrebbe essere utilizzato sull'oggetto invece del metodo . Le informazioni su ciò che dovrebbe accadere dopo non possono inizialmente essere lette nel luogo dell'evento stesso. repaint()

Confronti

Per comprendere meglio i concetti di pointcuts , join points e consigli, vedere i confronti con i vecchi paradigmi di seguito

  • SQL ( Structured Query Language )
    Immagina un'istruzione SELECT in grado di selezionare tutti i metodi in tutte le classi di un sistema. In questo contesto
    - un pointcut del del WHERE clausola, che limita il numero di metodi scelti
    - un join punto corrisponde ad un risultato di ricerca specifica nel senso di un record di dati da un database di - solo che nessun record di dati vengono selezionati da una banca dati, ma i metodi in un sistema
    - un consiglio è quindi il metodo in un aspetto che deve essere eseguito prima, dopo o al posto del metodo selezionato.
  • Altri paradigmi di programmazione
    Mentre nella programmazione procedurale la tracciabilità è fornita da un frammento di testo sorgente e nella programmazione orientata agli oggetti con una conoscenza aggiuntiva dei tipi di runtime, la tracciabilità nella programmazione orientata agli aspetti richiede la conoscenza di tutti gli aspetti, il punto taglia per i punti di unione del Definisci frammento di codice.
    Ortogonale qui significa che le proprietà dei metodi sono definite "perpendicolari" alla normale direzione di programmazione. L'effettiva esecuzione del codice non è solo definita dalla gerarchia delle chiamate e dei tipi, ma anche “perpendicolare” (ortogonalmente) ad essa dagli aspetti.

analogia

Il principio può essere visualizzato come segue: un programma, indipendentemente dal fatto che sia procedurale o orientato agli oggetti, segue un diagramma di flusso del programma , i. H. il flusso del programma è definito in ogni punto da sequenze lineari di istruzioni ( blocchi di codice ) e salti tra di loro (ad es. chiamate di metodo). Un aspetto qui sarebbe tanto quanto un modello che viene posizionato su questo piano originale e apporta varie modifiche o aggiunte al diagramma di flusso. Le modifiche al modello lasciano intatto il piano originale, il modello può essere cambiato in qualsiasi momento, combinato con altri o rimosso di nuovo.

Considerazione tecnica

In un ambiente di runtime orientato agli oggetti, la programmazione orientata agli aspetti potrebbe essere resa possibile da vettori di salto modificabili . Può essere visto come una sorta di “patching” di funzioni previste dal paradigma di programmazione.

Un oggetto C è abilitato a monitorare le interazioni tra due oggetti A e B senza che siano necessarie modifiche o estensioni ad A e B. Naturalmente, è effettivamente necessario un cambiamento in A o B o in entrambi. AspectJ crea queste modifiche automaticamente, il processo per farlo è chiamato tessitura , poiché tali modifiche sono "intrecciate" nel codice originale prima della compilazione.

Aree di applicazione

La programmazione orientata agli aspetti è in grado di sostituire completamente la programmazione guidata dagli eventi (gestione degli eventi) precedentemente utilizzata nella programmazione orientata agli oggetti . La programmazione guidata dagli eventi viene utilizzata per notificare a un oggetto X le modifiche a un oggetto Y. L'oggetto Y non ha bisogno di conoscere l'oggetto X. La soluzione precedente è spiegata qui utilizzando l'esempio di una finestra in Java (java.awt.Frame). Gli eventi che si verificano specificatamente per le finestre e per i quali un'altra parte del programma deve essere notificata includono la chiusura, l'attivazione e la disattivazione. Un'interfaccia java.awt.event.WindowListener definisce diversi metodi per questo e deve essere implementata dagli oggetti che vogliono essere informati delle modifiche alla finestra. Gli oggetti che vogliono essere notificati devono registrarsi con il rispettivo altro oggetto.

La programmazione orientata agli aspetti può rendere superflua la definizione di tali interfacce. Un aspetto X definisce gli eventi di codice da monitorare precisamente per l'oggetto Y da monitorare, chiamato point-cut, composto da punti di unione (intero metodo, chiamata di metodo, ritorno di metodo, distinguibili in ritorno di metodo con valore di ritorno e ritorno di metodo con eccezione), e definisce i consigli per i vari tagli di punti, ovvero il codice da eseguire. L'esecuzione di codice in X tramite modifiche a un oggetto Y può quindi avvenire senza interfacce, metodi e meccanismi di registrazione aggiuntivi.

La programmazione orientata agli aspetti può essere utilizzata nello sviluppo di framework (librerie) ad es. B. per implementare proprietà come la persistenza o la sincronizzabilità. Il meccanismo di trasferimento rimane quindi nascosto all'utente della libreria. Nascondere il meccanismo di trasferimento rende il codice più chiaro in questo caso, poiché i metodi non sono sovraccaricati con il codice del framework.

Un'altra area di applicazione è il test del software, dove l'introduzione di nuovi attributi nelle classi senza modificare il loro testo sorgente (dichiarazioni inter-tipo) rappresenta nuove e interessanti possibilità per lo sviluppo di test white box, ad es. B. per tracciare attributi privati.

Joinpoint vs Joinpoint shadow

È necessario fare una distinzione tra i punti di unione e le cosiddette ombre dei punti di unione. Un'ombra del punto di unione è l'occorrenza statica di un potenziale punto di unione. Se questa ombra (ad esempio una chiamata al metodo nel codice sorgente) diventa effettivamente un punto di unione viene deciso solo in fase di esecuzione a seconda del corrispondente pointcut (o designatore pointcut).

Un pointcut definisce un insieme di ombre di joinpoint che ritaglia dal programma sottostante. Se un'ombra del punto di unione viene inserita mentre il programma è in esecuzione e il taglio del punto di definizione può essere soddisfatto, l '"ombra del punto di unione" diventa il "punto di unione".

esempio

Il seguente esempio spiega l'idea di base della programmazione orientata agli aspetti. Il linguaggio di programmazione utilizzato è AspectJ , che estende Java per includere l'orientamento dell'aspetto.

Esempio introduttivo

Un'attività standard nello sviluppo del software servirà come esempio introduttivo: tracciare le informazioni in un file. La procedura senza programmazione orientata agli aspetti consiste nel creare un logger e chiamare lì un metodo corrispondente che salva le informazioni effettive nel file di registro:

public void eineMethode() {
    logger.trace("Betrete \"eineMethode\"");

    // Abarbeitung der Methode
    m = a + 2;

    logger.trace("Verlasse \"eineMethode\"");
}

All'inizio del metodo, viene inviato al logger un messaggio che informa che il metodo è stato immesso. Quindi segue la logica effettiva del metodo. Infine, viene registrata l'uscita dal metodo.

In un'applicazione tipica, tali chiamate di metodo al logger sono disponibili in molti metodi e classi: sono sparse in tutta l'applicazione e non sono affatto modulari. Il logger deve

  • reso noto a ciascun oggetto, e
  • non può essere facilmente scambiato in un punto centrale - almeno non senza dichiararlo nel sistema public static, che equivale a una variabile globale e quindi a un progetto dubbio.

Ciò rende anche chiaro cosa si intende per sezioni di programma semanticamente e fisicamente indipendenti . Nel metodo sopra, due attività effettivamente indipendenti sono confuse. Si tratta da un lato del logging e dall'altro della logica effettiva del metodo, che consiste nel memorizzare il risultato di un'addizione nella variabile membro m .

La programmazione orientata agli aspetti ora consente di modulare attività come la traccia. Supponiamo di voler registrare l'entrata e l'uscita di ogni metodo nella classe come mostrato sopra. Rispetto alla programmazione convenzionale, tale istruzione può essere formulata direttamente come un aspetto nell'AOP :

public aspect Tracing {
    pointcut traceCall():
        call(* AOPDemo.*(..));

    before(): traceCall() {
        System.out.println("Betrete \"" + thisJoinPoint + "\"");
    }

    after(): traceCall() {
        System.out.println("Verlasse \"" + thisJoinPoint + "\"");
    }

}

L'aspetto specifica che tutti i metodi della classe AOPDemo devono essere inclusi indipendentemente dalla loro firma . Le attività sono separate in questo modo e il metodo originale può essere abbreviato:

public void eineMethode() {
    // Abarbeitung der Methode
    m = a + 2;
}

Ulteriore esempio

Inoltre, la programmazione orientata agli aspetti consente di reindirizzare la sequenza del programma originale. Gli aspetti di sicurezza possono, ad esempio, sostituire la sequenza del programma originale per impedire l'accesso non autorizzato alle parti del programma protette. Gli aspetti della memorizzazione nella cache aumentano la velocità di esecuzione dei programmi e vengono utilizzati per ridurre la necessità di richiamare parti di programma complesse come l'accesso al database o al file system. L'esempio seguente mostra il reindirizzamento delle chiamate di metodo o lo scambio di parti di programma come intercettazione intorno ai consigli:

public aspect Caching {

    pointcut cacheCall():
        call(* AOPDemo.*(..));

    private Map cache = new Map();

    around(): cacheCall(Joinpoint joinPointContext) {

       // Prüfen, ob Rückgabewert für aktuelle Aufruf-Argumente schon im Cache abgelegt wurde
       Object args = joinPointContext.getArguments();
       boolean isCallCached = cache.containsKey(args);

       if (isCallCached) {
             // Umleitung und Austausch des ursprünglichen Methodenaufrufs, gesicherten Rückgabewert aus Cache verwenden
             Object cachedReturnValue = cache.get(args);
             return cachedReturnValue;
       }
       else {
             // Weiterleitung an ursprüngliche Methode und neuen Rückgabewert im Cache sichern
             Object newReturnValue = joinPointContext.proceed();
             cache.put(args, newReturnValue);
             return newReturnValue;
       }
    }
}

Termini

L'esempio include già i concetti più importanti, se non tutti. In questa sezione vengono aggiunti quelli mancanti e assegnati ai termini utilizzati nell'AOP.

A tale scopo, l'esempio viene ampliato dalla seguente sequenza di codice e viene visualizzato graficamente il processo precedente:

public void quellMethode() {
    eineMethode();
}

Rappresentazione schematica dell'uso di un aspetto

L'aspetto entra in gioco ancor prima di eineMethode()entrare. La ragione di ciò è il punto di unione proprio di fronte ad esso. Questi punti di unione vengono forniti implicitamente. Cioè, sono presenti prima di ogni metodo. Il modello che seleziona quei punti di unione da tutti i punti di unione esistenti che sono interessanti per un aspetto è chiamato taglio di punti . In effetti, i pointcuts sono modelli che consentono anche i caratteri jolly . Al fine di determinare quando quale codice deve essere eseguito all'interno dell'aspetto, entrano in gioco i consigli . Nell'esempio introduttivo questi sono prima e dopo , nell'ulteriore esempio intorno . Questi consigli, tra gli altri, sono forniti implicitamente.

La programmazione con aspetti consente inoltre di modificare il comportamento delle classi in e con aspetti. Gli aspetti possono aggiungere campi e metodi alle classi. Anche qui è possibile utilizzare caratteri jolly per più classi contemporaneamente. In un linguaggio come Java, queste dichiarazioni tra tipi violano la regola che tutti i campi e metodi di una classe possono essere trovati in un file o la gerarchia di ereditarietà della classe, poiché alcuni aspetti di questo non appartengono.

Compiti tipici

AOP è particolarmente adatto per la programmazione dei cosiddetti problemi trasversali . Gli esempi includono registrazione , gestione degli errori , persistenza , convalida dei dati e sicurezza IT .

Le API di profilazione , come quelle contenute in Java , funzionano in modo simile a AOP. Sono utilizzati per determinare i codici non performanti. A tale scopo, le misurazioni dei tempi per l'elaborazione di tutti i metodi vengono effettuate da un cosiddetto profiler. Il profiler effettivo può essere informato dalla macchina virtuale quando si entra e si esce da un metodo. Un profiler può quindi essere implementato anche con una programmazione orientata agli aspetti.

Approcci simili

Gli aspetti hanno la loro origine nella programmazione orientata agli oggetti e, almeno nella loro intenzione, sono paragonabili ai protocolli meta-oggetto, come quelli che si trovano nel Common Lisp Object System , per esempio . Inoltre, gli aspetti sono legati a concetti come la programmazione orientata al soggetto , i mixin , i classbox o il concetto di delega , come si può trovare nel linguaggio di programmazione Self . I cosiddetti tratti rappresentano un approccio simile o addirittura equivalente .

Osservazioni

Il vantaggio dell'orientamento dell'aspetto è la separazione logica e fisica della semantica (il componente) dal dettaglio tecnico (aspetto). Lo svantaggio della programmazione orientata agli aspetti è l' overhead che si verifica dopo la tessitura nel programma generato. Questo generalmente porta a una perdita di prestazioni . Inoltre, la programmazione orientata agli aspetti riduce la tracciabilità del comportamento del programma, poiché i luoghi in cui un aspetto è responsabile non sono direttamente riconoscibili nel codice interessato. Il debug è reso molto più difficile, ma questo svantaggio può essere neutralizzato o almeno ridotto supportando un IDE , in quanto il debug è multidimensionale quanto lo sviluppo del codice.

Un altro svantaggio è che quando si utilizza questa tecnica, possono verificarsi interazioni indesiderabili e difficili da comprendere tra i singoli aspetti.

Aspetti e componenti possono essere definiti in diversi linguaggi di programmazione.

Guarda anche

letteratura

  • Lars Wunderlich: AOP: programmazione orientata agli aspetti nella pratica. Developers.press, Francoforte sul Meno 2005, ISBN 3-935042-74-4 .
  • Oliver Böhm: Programmazione orientata agli aspetti con AspectJ 5: Introduzione a AspectJ e AOP. Dpunkt Verlag, Heidelberg 2006, ISBN 3-89864-330-1 .
  • Renaud Pawlak, Lionel Seinturier, Jean-Philippe Retaille: Fondamenti di AOP per lo sviluppo J2EE (Fondazione). Apress, Berkeley CA 2005, ISBN 1-59059-507-6 .
  • Ralf Westphal: Programmazione orientata agli aspetti con .NET. In: dotnetpro Ulm 22.2007,10. ISSN  1610-1553

link internet

Prove individuali

  1. ^ Intercettazione intorno al consiglio