Aspektorientierte Programmierung (AOP) ist ein Programmierparadigma für die objektorientierte Programmierung, um generische Funktionalitäten über mehrere Klassen hinweg zu verwenden (Cross-Cutting Concern). Logische Aspekte eines Anwendungsprogramms werden dabei von der eigentlichen Geschäftslogik getrennt. Typische Anwendungsbeispiele sind Transaktionsverwaltung, Auditfähigkeit und Loggingverhalten.

Das Konzept von AOP wurde von Gregor Kiczales und seinem Team am Xerox PARC entwickelt. Im Jahr 2001 wurde dort auch die erste AOP-Sprache AspectJ vorgestellt.

Motivation Software hat grundsätzlich bestimmte Aufgaben/Anforderungen zu erfüllen. Diese Anforderungen kann man grob in zwei Bereiche gliedern: Die sogenannten Core-Level-Concerns (betreffen den logischen „Kern“ der Anwendung) oder funktionale Anforderungen. Dies sind Anforderungen an die Software, die man meist gut in einzelnen Funktionen kapseln kann. Ein Beispiel wäre die Berechnung eines Wertes. Die System-Level-Concerns (betreffen das gesamte System) oder technische Randbedingungen. Diese Anforderungen können nicht einfach gekapselt werden, da sie an vielen Stellen implementiert werden müssen. Ein Paradebeispiel dafür ist das Logging, die Protokollierung des Programmablaufs in Logdateien. Der Aufruf des Loggers ist für die eigentliche Funktionalität nicht notwendig, muss aber trotzdem in den Quelltext integriert werden. Ein weiteres Beispiel wäre die Transaktionierung von Zugriffen auf eine Ressource wie z. B. eine Datenbank.

Diese beiden Teile sind miteinander verwoben. Die Core-Level-Concerns kann man als Komponenten bezeichnen, und die System-Level-Concerns sind die Aspekte. Core-Level-Concerns werden üblicherweise als Module oder Objekte implementiert. Für die Aspekte gab es vor der aspektorientierten Programmierung keine elegante Lösung.

Das Problem der miteinander verwobenen Anforderungen wird auch als Cross-Cutting Concerns bezeichnet, denn sie „schneiden“ quer durch alle logischen Schichten des Systems. AOP ist das Werkzeug, um die logisch unabhängigen Belange auch physisch voneinander zu trennen. Dabei wird angestrebt, Code zu erzeugen, der besser wartbar und wiederverwendbar ist.

In diesem Zusammenhang sind System-Level-Concerns aber nicht gleichzusetzen mit rein technischen Belangen. Auch fachliche Belange wie z. B. die Umsetzung eines eigenen, fachlichen Berechtigungssystems für Benutzer können durchaus Aspektcharakter haben.

Hintergrund In der Geschichte der Entwicklung der Programmiersprachen wurden immer wieder neue Konzepte der Programmierung und diese implementierende Hochsprachen entwickelt, angefangen bei der Programmierung in Assembler, welche das direkte Programmieren in Binärcode ablöste, über prozedurales und funktionales Programmieren, bis hin zu den heute aktuellen objektorientierten Sprachen. Der Zweck ist, den Entwicklern die Arbeit zu erleichtern und somit eine bessere Effizienz in der Entwicklung der Software zu erzielen. Eines der zugrundeliegenden Prinzipien, das bei allen neuen Programmierkonzepten angewendet wurde, war das der Kapselung von Funktionalität.

Assemblersprachen kapseln Binärbefehle für Arbeitsspeicher- und Register­zugriffe in kurzen, generalisierten Zeichenketten und befreien den Softwareentwickler damit von der Notwendigkeit, sich mit der Binärcodierung zu beschäftigen. Prozedurale Sprachen erlauben die Kapselung einfacher Funktionen, wie das Sortieren einer Namensliste, innerhalb einer Prozedur. Damit entfällt die Notwendigkeit von Zeilennummern und daran gebundenen Sprungbefehlen sowie das mehrfache Entwickeln derselben Funktion an verschiedenen Stellen des Programmcodes. Objektorientierte Programmiersprachen erlauben die Kapselung konzeptionell zusammengehöriger Funktionen und Variablen in unabhängigen Modulen (Klassen) mit klar definierten Schnittstellen, wodurch die Wiederverwendbarkeit der einzelnen Module erhöht wird und der Softwareentwickler davon befreit ist, sich mit der internen Implementierung der von ihm verwendeten Klassen auseinanderzusetzen.

Eine erweiterte Funktionalität zu ermöglichen, ist nicht Ziel dieser Entwicklung, weil jede Hochsprache nach der Kompilierung auf Maschinencode abgebildet wird.

Insbesondere erleichtert die Kapselung von Funktionalität die Entwicklung von Software, indem Wartbarkeit und Wiederverwendbarkeit existierenden Programmcodes erhöht werden. Da Software im Laufe der Zeit immer komplexer und umfangreicher wird, und damit die Entwicklung zeitaufwendiger und teurer, gewannen diese beiden Ziele immer mehr an Bedeutung und sind heute zentrale Elemente bei der Entwicklung neuer Programmierkonzepte und -sprachen.

Bei AOP (aspect-oriented programming bzw. aspektorientierte Programmierung) handelt es sich um ein Programmierkonzept, welches das Problem der sogenannten Cross-Cutting Concerns behandelt.

Die kostengünstige und termingerechte Entwicklung und Wartung qualitativ hochwertiger Software ist das Primärziel des Software Engineering. Um dieses Ziel zu erreichen, ist eine möglichst modularisierte Software mit einer möglichst geringen Komplexität der Module notwendig.

In einem konventionellen System, wobei hier auch die objektorientierten Ansätze hinzugehören, können Kernfunktionalitäten, englisch core concerns, für sich allein betrachtet nach den Regeln der Kunst sauber in Module getrennt werden. Es gibt jedoch concerns (Anliegen, Belange, Anforderungen) wie Fehlerbehandlung, Performance und Sicherheit in jedem System, die die Kernfunktionalitäten quer schneiden (eng. cross cut) und sich deshalb nicht eindeutig einem Software-Modul zuordnen lassen. Dies führt dazu, dass Fragmente solcher cross cutting concerns (quer schneidende Kernfunktionalitäten (übergreifende Anforderungen) – fehlende Kohäsion) nicht zugeordnet und ungekapselt im ganzen Code verstreut sind. Diese quer schneidenden Kernfunktionalitäten verhindern in konventionellen Softwaresystemen eine saubere Modularisierung und beeinträchtigen Pflege, Verständlichkeit, Wiederverwendbarkeit und (Rück-)Verfolgbarkeit. Verantwortlich hierfür ist bei konventionellen Programmiersprachen die Systemdekomposition, die nur eine Dimension zulässt – die Liste von Funktionen. Dieses Phänomen nennt man auch dominante Dekomposition. Mit anderen Worten: ein natürlicherweise mehrdimensionales Problem muss eindimensional gelöst werden.

Ableitung aus früheren Paradigmen In der prozeduralen Programmierung ist die Ausführung von Code vergleichsweise linear. Durch Verfolgung der Symbole ist jeder einzelne Programmschritt auch bei der Betrachtung eines Teilsystems direkt nachvollziehbar. Beispiel in C:

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

Die Funktion Component_repaint() ist eindeutig. Unabhängig davon, ob der Zeiger c den dynamischen Typ Component oder einen abgeleiteten Typ hat, wird immer dieselbe Funktion Component_repaint(Component*) aufgerufen (statische Typisierung).

In der objektorientierten Programmierung wird die Nachvollziehbarkeit durch die Polymorphie reduziert. Beispiel in Java:

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

Es ist anhand des Quelltextes nicht nachvollziehbar, welche repaint()-Methode ausgeführt wird, das hängt vom Laufzeit-Typ des Objekts ab, das von c referenziert wird. Ein von Component abgeleiteter Typ könnte seine eigene repaint()-Methode definieren, indem er die von Component geerbte Methode repaint() überschreibt.

In der aspektorientierten Programmierung wird die Nachvollziehbarkeit durch die Verwendung von Pointcuts weiter reduziert. Ein Pointcut enthält für einen Join-Point auszuführenden Code, ein Join-Point ist dabei ein genau definiertes Aufrufereignis. Hier können zu nahezu beliebigen Punkten in der Aufrufkette Advices aktiviert werden. Damit ist es im Extremfall sogar möglich, den Aufruf der Methoden function() oder repaint() an sich zu verhindern. So könnte man sich einen Aspekt vorstellen, der für den Join-Point „Aufruf der Methode function()“ einen Advice definiert, der explizit die weitere Abarbeitung dieses Funktionsaufrufs untersagt. Ebenso könnte man in einem Advice festlegen, dass anstatt der Methode repaint() eine andere Methode auf das Objekt angewandt werden soll. Die Information darüber, was als Nächstes geschehen soll, ist dabei am Ort des Geschehens selbst zunächst nicht abzulesen.

Vergleiche Zum besseren Verständnis der Konzepte von Pointcuts, Join-Points und Advices dienen die Vergleiche mit den nachstehenden älteren Paradigmen.

Structured Query Language (SQL) Man stelle sich ein SELECT-Statement vor, das alle Methoden in allen Klassen eines Systems selektieren kann. In diesem Zusammenhang entspricht dann ein Pointcut der WHERE-Klausel, welche die Menge der selektierten Methoden einschränkt ein Join-Point einem konkreten Suchergebnis im Sinne eines Datensatzes aus einer Datenbank – nur, dass hier keine Datensätze aus einer Datenbank selektiert werden, sondern Methoden in einem System ein Advice der Methode in einem Aspekt, die vor, nach oder anstatt der ausgewählten Methode ausgeführt werden soll.

Andere Programmierparadigmen Während in der prozeduralen Programmierung die Nachvollziehbarkeit durch ein Quelltextfragment und in der objektorientierten Programmierung unter zusätzlicher Kenntnis über die Laufzeittypen vergleichsweise direkt gegeben ist, erfordert die Nachvollziehbarkeit in der aspektorientierten Programmierung die Kenntnis sämtlicher Aspekte, die Point-Cuts für die Join-Points des Code-Fragment definieren.

Orthogonal bedeutet hier, dass Eigenschaften von Methoden „senkrecht“ zur normalen Programmierrichtung definiert werden. Die tatsächliche Code-Ausführung wird nicht nur durch die Aufruf- und Typenhierarchie, sondern zusätzlich „senkrecht“ (orthogonal) dazu von den Aspekten definiert.

Analogie Anschaulich kann man sich das Prinzip wie folgt vorstellen: ein Programm, gleichgültig ob prozedural oder objektorientiert, folgt einem Programmablaufplan, d. h. der Programmfluss ist an jeder Stelle durch lineare Folgen von Anweisungen (Codeblöcke) und Sprünge zwischen diesen (z. B. Methodenaufrufe) festgelegt. Ein Aspekt wäre hier so viel wie eine Schablone, die über diesen Originalplan gelegt wird und verschiedene Änderungen oder Ergänzungen am Ablaufschema vornimmt. Änderungen an der Schablone lassen den Originalplan unangetastet, die Schablone lässt sich jederzeit austauschen, mit anderen kombinieren oder wieder entfernen.

Technische Betrachtung In einer objektorientierten Laufzeitumgebung könnte aspektorientierte Programmierung durch veränderbare Sprungvektoren ermöglicht werden. Man kann sie als eine Art im Programmierparadigma vorgesehenes „Patchen“ von Funktionen ansehen.

Ein Objekt C wird in die Lage versetzt, Interaktionen zwischen zwei Objekten A und B zu überwachen, ohne dass dafür Veränderungen oder Erweiterungen am Quelltext, der A und B implementiert, notwendig sind. Natürlich ist tatsächlich doch eine Veränderung von A oder B oder beiden notwendig. AspectJ erzeugt diese Änderungen automatisch, der Vorgang dafür heißt Weaving, da besagte Änderungen vor dem Kompilieren in den Originalcode „eingewebt“ werden.

Einsatzgebiete Die aspektorientierte Programmierung ist in der Lage, die bisher in der objektorientierten Programmierung eingesetzte ereignisgesteuerte Programmierung (Event-Handling) ganz zu ersetzen. Die ereignisgesteuerte Programmierung dient dazu, ein Objekt X über Veränderungen an einem Objekt Y zu benachrichtigen. Das Objekt Y braucht das Objekt X dabei aber nicht zu kennen.