Rheinwerk Computing < openbook > Rheinwerk Computing - Professionelle Bücher. Auch für Einsteiger.
Professionelle Bücher. Auch für Einsteiger.

Inhaltsverzeichnis
1 Einleitung
2 Die Basis der Objektorientierung
3 Die Prinzipien des objektorientierten Entwurfs
4 Die Struktur objektorientierter Software
5 Vererbung und Polymorphie
6 Persistenz
7 Abläufe in einem objektorientierten System
8 Module und Architektur
9 Aspekte und Objektorientierung
10 Objektorientierung am Beispiel: Eine Web-Applikation mit PHP 5 und Ajax
A Verwendete Programmiersprachen
B Literaturverzeichnis
Stichwort
Ihre Meinung?

Spacer
 <<   zurück
Objektorientierte Programmierung von Bernhard Lahres, Gregor Rayman
Das umfassende Handbuch
Buch: Objektorientierte Programmierung

Objektorientierte Programmierung
2., aktualisierte und erweiterte Auflage, geb.
656 S., 49,90 Euro
Rheinwerk Computing
ISBN 978-3-8362-1401-8
Pfeil 9 Aspekte und Objektorientierung
  Pfeil 9.1 Trennung der Anliegen
    Pfeil 9.1.1 Kapselung von Daten
    Pfeil 9.1.2 Lösungsansätze zur Trennung von Anliegen
  Pfeil 9.2 Aspektorientiertes Programmieren
    Pfeil 9.2.1 Integration von aspektorientierten Verfahren in Frameworks
    Pfeil 9.2.2 Bestandteile der Aspekte
    Pfeil 9.2.3 Dynamisches Crosscutting
    Pfeil 9.2.4 Statisches Crosscutting
  Pfeil 9.3 Anwendungen der Aspektorientierung
    Pfeil 9.3.1 Zusätzliche Überprüfungen während der Übersetzung
    Pfeil 9.3.2 Logging
    Pfeil 9.3.3 Transaktionen und Profiling
    Pfeil 9.3.4 Design by Contract
    Pfeil 9.3.5 Introductions
    Pfeil 9.3.6 Aspektorientierter Observer
  Pfeil 9.4 Annotations
    Pfeil 9.4.1 Zusatzinformation zur Struktur eines Programms
    Pfeil 9.4.2 Annotations im Einsatz in Java und C#
    Pfeil 9.4.3 Beispiele für den Einsatz von Annotations

In diesem Kapitel stellen und klären wir die Frage, ob aspektorientierte Programmierung ein neues Paradigma ist oder eine Ergänzung zu objektorientierten Techniken. Wir nehmen die Antwort hier schon einmal ganz kurz vorweg: Aus unserer Sicht ist Aspektorientierung eine Ergänzung der Objektorientierung, die in Zukunft einige bestehende Defizite ausbügeln könnte.

9 Aspekte und Objektorientierung


Rheinwerk Computing - Zum Seitenanfang

9.1 Trennung der Anliegen  Zur nächsten ÜberschriftZur vorigen Überschrift

Für die Beherrschung der Komplexität ist das Prinzip Teile und herrsche das Wesentliche. Es geht darum, komplexe Systeme in einfachere Bestandteile zu zerlegen und diese von dem Gesamtsystem unabhängig einsetzbar zu machen.

Idealerweise wird die Komplexität der Programme von der Komplexität der Anforderungen etwa linear abhängig sein. [Wir sind uns natürlich klar darüber, dass sich über eine ideale Programmiersprache lange diskutieren lässt. Wir beziehen uns hier lediglich darauf, wie sich Anforderungen, die mit einem Programm umgesetzt werden sollen, auf das Programm selbst abbilden lassen. Dabei wird das Programm selbst nicht einfacher werden, als es die inhärente Komplexität der Anforderungen zulässt. Die ideale Sprache würde aber keine zusätzliche Komplexität durch technische Restriktionen hinzufügen. ]


Die ideale Programmiersprache

Eine ideale Programmiersprache wäre eine solche, in die man die an das Programm gestellten funktionalen Anforderungen einfach direkt übersetzen könnte. Eine zusammenhängend beschriebene funktionale Anforderung sollte in einen zusammenhängenden Abschnitt der Quelltexte – ein Modul – überführt werden können. In so einer Sprache wäre ein Modul nur dann von einem anderen Modul abhängig, wenn die korrespondierenden fachlichen Anforderungen ebenfalls abhängig sind.


Die Objektorientierung bietet hier viele Möglichkeiten. Durch die dynamische Polymorphie können wir die Abhängigkeiten abstrahieren, so dass Module nicht von konkreten Implementierungen, sondern von abstrakten Schnittstellen abhängig sind. Sie können gemeinsame Funktionalität in mehrfach verwendbare Module auslagern und Spezialfunktionalität in separaten Erweiterungsmodulen bereitstellen.

Trennung der Anliegen – keine Stärke der Objektorientierung

Doch ein Problem hat auch die Objektorientierung nicht zufrieden stellend gelöst: die Trennung der Anliegen. Die Strukturierung eines objektorientierten Systems wird anhand von Objekten und ihren Klassen vorgenommen. Man weist den Klassen bestimmte Verantwortungen zu und implementiert ihre Funktionalität in entsprechenden Quelltextmodulen.

In gut entworfenen Systemen hat jede Klasse einen klar definierten Zweck, sie wurde entworfen, weil sie im System eine Verantwortung trägt und für eine Aufgabe zuständig ist.

Problem: Wo wird meine Anforderung umgesetzt?

Das Problem in einem objektorientierten System ist, dass eine Klasse sich außer um ihre Primäraufgabe auch um andere Anliegen kümmern muss. Wenn Sie zum Beispiel eine sicherheitsrelevante Anwendung schreiben, dürfen bestimmte Aktionen nur durch dafür vorgesehenen Benutzer durchgeführt werden. In jeder Methode, die eine solche Aktion auslöst, muss also überprüft werden, ob der aktuelle Benutzer sie überhaupt durchführen darf. Sie können zwar die Funktionalität der Überprüfung in eine dafür vorgesehene Klasse verlagern, den Aufruf der Überprüfung jedoch nicht.

Auf diese Art sind Sie also gezwungen, eine einfache Anforderung wie »Nur Benutzer der Sicherheitsstufe B dürfen die Stammdaten eines Kunden ändern« an vielen Stellen Ihrer Quelltexte einzubauen: In der Methode, die den Namen eines Kunden ändert, in der Methode, die einem Kunden eine neue Adresse zuordnet, und in vielen anderen Methoden.

Die Anforderung lässt sich mit den Mitteln der Objektorientierung nicht einer einzigen Stelle im Programm zuordnen. Objektorientierte Programmiersprachen entsprechen also nicht unserem Ideal einer Programmiersprache.

Problem: Welche Anforderung setze ich hier um?

Doch auch wenn unsere Programmiersprache nicht ideal ist, ist es natürlich möglich, die beschriebene Anwendung inklusive Sicherheitsüberprüfungen erfolgreich zu erstellen. Nehmen Sie nun aber an, dass sich später zeigt, dass Ihre Sicherheitsmaßnahmen nicht ausreichend waren. Änderungen an den Kundendaten konnten zwar nur überprüfte Benutzer der Sicherheitsstufe B durchführen, da Sie aber nicht nachvollziehen können, wer welche Änderung vorgenommen hat, haben manche Benutzer ihre Privilegien missbraucht und ihre Freunde zu VIP-Kunden gemacht. Ihr Auftraggeber stellt also vernünftigerweise eine neue Anforderung: »Jede Änderung an Kundendaten muss mit Namen des Benutzers protokolliert werden.« Und da der Betriebsrat auch sein O.k. gegeben hat, müssen Sie wieder jede Menge Methoden anfassen und um die entsprechenden Aufrufe der Protokollierung erweitern. Glücklicherweise haben Sie dabei den Zugriff auf die Quelltexte der Klasse Kunde, weil Sie diese selbst entwickelt haben.

Die Methoden der Klasse Kunde sehen jetzt in etwa so aus: [Das hier ist kein echter Quelltext und auch keine echte Programmiersprache. ]

Klasse Kunde { 
   namenÄndern(neuerName) { 
      erlaubt?(aktuellerBenutzer, 'kundenNamenÄndern') 
         nein R Fehler Melden; 
      protokolliere(aktuellerBenutzer, 
         'kundenNamenÄndern', 
         this.id, this.name, neuerName); 
      this.name = neuerName; 
   } 
   statusÄndern(neuerStatus) { 
      erlaubt?(aktuellerBenutzer, 'kundenStatusÄndern') 
         nein R Fehler Melden; 
      protokolliere(aktuellerBenutzer, 
         'kundenStatusÄndern', 
         this.id, this.status, neuerStatus); 
      this.status = neuerStatus; 
   } 
}

Listing 9.1    Mehrere Anliegen vermischt

Nun haben die Methoden, deren Primäraufgabe es ist, den Namen beziehungsweise den Status eines Kunden zu ändern, mehr Quelltext, der für andere Anliegen zuständig ist als für ihre eigentliche Aufgabe. Glücklicherweise können Sie aus den Quelltexten und den sprechenden Namen der Methoden noch erkennen, wozu sie eigentlich da sind. In realen Programmen ist das nicht immer der Fall.

Diese Verunreinigung von Code durch Bestandteile, die nichts mit den eigentlichen Aufgaben des betrachteten Moduls zu tun haben, wird als Code Tangling [Code Tangling kann etwa mit »Code-Durcheinander« oder auch mit »durcheinander gewürfelter Code« übersetzt werden. ] bezeichnet.


Icon Hinweis Code Tangling (Code-Durcheinander)

Als Code Tangling wird es bezeichnet, wenn Code, der verschiedene Anliegen betrifft, in einem Modul vermischt wird. Code Tangling führt dazu, dass die betroffenen Module das Prinzip einer einzigen Verantwortung verletzen. Dadurch ist der betroffene Code schlecht wiederverwendbar. Zusätzlich leidet die Verständlichkeit des Codes, da der Ablauf aufgrund der verschiedenen Anliegen häufig nicht klar erkennbar ist.


In unserem Beispiel lässt sich auch ein weiteres Problem beobachten. Der Code für die Umsetzung der Sicherheitsüberprüfungen und für die Protokollierung findet sich in gleichartiger Form in mehreren ansonsten unabhängigen Modulen wieder. Der Code ist damit über mehrere Module verstreut. Der englische Begriff dafür ist Code Scattering. Dieser verstreute Code kann natürlich seine Ursache auch einfach in schlechtem Moduldesign haben. In unserem Beispiel haben Sie aber mit den klassischen Mechanismen der Objektorientierung gar keine Chance, dieses Verstreuen zu vermeiden.


Icon Hinweis Code Scattering (Code-Streuung)

Code Scattering liegt vor, wenn ein Anliegen über mehrere Module verteilt ist. Code Scattering führt dazu, dass Code häufig redundant in verschiedenen Modulen vorliegt. Auch hierdurch wird das Prinzip einer einzigen Verantwortung verletzt. Es gibt mehrere Module, die Verantwortung für das gleiche Anliegen tragen. Es wird damit sehr schwer herauszufinden, von welchen Modulen eine bestimmte Anforderung umgesetzt wird.


Sie finden in unserem Beispiel also sowohl Code Tangling als auch Code Scattering. Liegt also einfach ein schlechtes Moduldesign vor?

Crosscutting Concerns

Die beiden Anliegen der Bearbeitung von Kundendaten und der Überprüfung von Sicherheitsaspekten lassen sich mit den zur Verfügung stehenden Verfahren aber überhaupt nicht in eine eindeutige Nutzungsbeziehung zwischen Modulen bringen. Solche Anliegen (Concerns) nennt man Crosscutting Concerns.


Icon Hinweis Crosscutting Concerns (übergreifende Anliegen)

Crosscutting Concerns sind Anliegen in einem System, für die es keine Zerlegung in Module gibt, in denen eines der Anliegen als unabhängig vom anderen betrachtet werden kann. Die Anliegen liegen quer zueinander. Damit ist es nicht möglich, sie in eine hierarchische Struktur zu bringen.


In unserem Beispiel unterscheiden sich die Abhängigkeiten der Module von den Abhängigkeiten der Anforderungen: In unseren Anforderungen beziehen sich die Sicherheitsanforderungen auf die Fachanforderungen. In unseren Quelltexten ist das umgekehrt, unsere Fachmethoden müssen sich um die Sicherheitsanliegen mitkümmern. Diese Situation ist in Abbildung 9.1 dargestellt.

Abbildung 9.1    Umkehr der Abhängigkeit der Aspekte in den Quelltexten

Fazit: Die Objektorientierung, wenn sie auch viele Probleme der Komplexität angeht, ist hier nicht die ideale Vorgehensweise.


Rheinwerk Computing - Zum Seitenanfang

9.1.1 Kapselung von Daten  Zur nächsten ÜberschriftZur vorigen Überschrift

Ein anderes Problem bringt die Kapselung der Daten mit sich. Durch die Kapselung der Datenstrukturen einer Klasse wird verhindert, dass andere Teile der Anwendung unkontrolliert auf sie zugreifen. So wird die Komplexität der Anwendung verringert und ihre Änderbarkeit erhöht. Das ist der positive Beitrag der Objektorientierung. Doch er hat seinen Preis: Wenn wir die Daten der Exemplare dieser Klasse zum Beispiel in einer Datenbank speichern möchten, muss diese Persistenzfunktionalität ebenfalls in der Klasse implementiert werden – denn niemand außer der Klasse selbst darf auf ihre Datenstrukturen zugreifen.

So muss sich unsere Klasse neben ihrer Primäraufgabe auch um das Anliegen der Persistenz kümmern. Dies hat mehrere Nachteile:

Mehrere Änderungsgründe

  • Ändern sich die Anforderungen an eines dieser Anliegen, muss die Klasse angepasst werden. Das widerspricht aber unserem Wunsch, dass eine Änderung eines Quelltextmoduls nur durch eine Änderung in einem Anforderungsbereich erzwungen werden sollte.

Unnötige Abhängigkeiten

  • Nur weil Sie in einem Kontext die Persistenzfunktionalität brauchen, müssen Sie die Klasse erweitern. In anderen Kontexten wird die Persistenz aber vielleicht gar nicht benötigt. Nun müssen Sie aber, wenn Sie in beiden Kontexten dieselbe Klasse mehrfach verwenden möchten, die Persistenzfunktionalität, zumindest eine leere Implementierung, doch bereitstellen. Sie zwingen so Anwendungen, die keine Persistenz benötigen, von der Persistenz abhängig zu sein. Sie müssen zwar keine tatsächlich funktionierende Persistenzimplementierung bereitstellen, Sie müssen aber wissen, dass es so etwas wie Persistenz überhaupt gibt. Dies widerspricht unserem Wunsch, nur von explizit benötigten Schnittstellen abhängig zu sein.
  • Die Persistenz betrifft normalerweise nicht nur eine Klasse, sie betrifft verschiedene Klassen und muss also in verschiedenen Quelltextmodulen behandelt werden. Dies widerspricht jedoch unserem Wunsch, eine Anforderung in einem Quelltextmodul zu implementieren.

Aufbau des Kapitels

Was können Sie tun, um dieses Probleme in einem objektorientierten System anzugehen? Im nächsten Abschnitt 9.1.2 werden wir einige Ansätze zur Problemlösung vorstellen, die darauf aufbauen, Quelltexte zu generieren oder Informationen über die Klassenstruktur eines Programms auszuwerten. Diese Ansätze werden in der Praxis eingesetzt, haben aber selbst eine Reihe von Defiziten. In Abschnitt 9.2 werden wir deshalb zeigen, dass die aspektorientierte Programmierung eine ganze Reihe der anhand unseres Beispiels vorgestellten Probleme elegant löst. Anhand von weiteren Beispielen werden wir vorstellen, wie sich in Abschnitt 9.3 aspektorientierte Mechanismen einsetzen lassen, um die vorgestellten Defizite der Objektorientierung auszubügeln. Dabei unterstützen auch die sogenannten Annotations, Zusatzinformationen zu einem Programm, die Gegenstand von Abschnitt 9.4 sind.


Rheinwerk Computing - Zum Seitenanfang

9.1.2 Lösungsansätze zur Trennung von Anliegen  topZur vorigen Überschrift

Bleiben wir beim Beispiel der Persistenz-Funktionalität, und schauen wir uns verschiedene erprobte Möglichkeiten an, wie Sie die Persistenz in unseren Klassen umsetzen können. Wir gehen dabei davon aus, dass Sie die zugehörigen Daten in einer relationalen Datenbank speichern, und versuchen, dabei die Auswirkungen der genannten Unzulänglichkeiten der Objektorientierung zu minimieren.

Quelltextgenerierung

Wenn Objekte in einer relationalen Datenbank gespeichert werden, werden normalerweise die Klassen bestimmten Tabellen zugeordnet und deren Dateneinträge bestimmten Spalten in diesen Tabellen. Die konkreten Abbildungen der Objektstrukturen auf die Strukturen einer relationalen Datenbank haben wir bereits in Kapitel 6, »Persistenz«, beschrieben.

Abbildung Objekte auf Tabellen

Irgendwo in den Quelltexten muss definiert werden, welche Klassen und welche Attribute dieser Klassen welchen Tabellen und Spalten zugeordnet sind. Bei einem für unsere Anwendung entworfenen Datenmodell reicht diese Information meistens aus, um die Persistenz der Objekte implementieren zu können – die nötigen SQL-Befehle für das Lesen, Ändern, Anlegen und Löschen der Dateneinträge in der Datenbank können aus dieser Information abgeleitet werden.

Da aber nur die jeweiligen Klassen den Zugriff auf diese Daten haben, müssen diese SQL-Befehle in den jeweiligen Klassen implementiert werden. Ein Teil der Funktionalität kann sicherlich in einer gemeinsamen Basisklasse implementiert werden, andere Teile in einem anderen Modul, doch das Lesen und das Schreiben in die konkreten Attribute der Exemplare einer Klasse kann nur in der Klasse selbst oder, wenn es die Sichtbarkeitsregeln zulassen, in einer ihrer Unterklassen implementiert werden.

Da Sie aber mit der Zuordnung der Klassen zu den Datenbanktabellen und der Attribute zu den Datenbankspalten alle nötigen Informationen haben, wie die SQL-Befehle auszusehen haben, wäre es redundant, diese SQL-Befehle explizit schreiben zu müssen.

Ein Compiler verlangt aber, dass die Quelltexte der Klassen die Vorschrift für die Erzeugung der SQL-Befehle enthalten. Sie müssen zum Beispiel definieren, dass das Feld firstName der Exemplare der Klasse Person in der Spalte VORNAME der Tabelle Person gespeichert wird, und nur die Klasse Person hat den Zugriff auf ihre eigenen Felder.

Generierung von redundanten Quelltexten

Um diese Information nicht selbst in Ihre Quelltexte einfügen zu müssen, können Sie die redundanten Teile der Quelltexte aus dem Datenmodell und der Zuordnungsinformation generieren lassen. Die redundanten Teile werden also generiert, die Primärfunktionalität der Klasse programmieren wir wie vorher selbst.

Diese Vorgehensweise erfüllt die Zielsetzung, redundante Teile in den von Menschen erstellten Quelltexten zu meiden. Sie hat allerdings ein paar Stolperfallen, die sich aus der Tatsache ergeben, dass Sie generierte Quelltexte mit den selbst programmierten mischen müssen.

Stolperfallen der Quelltextgenerierung

Was ist ein Quelltext?

Das Wort »Quelltext« hat für uns zwei Bedeutungen. Einerseits ist es ein technischer Begriff, mit dem Textdateien gemeint werden, die ein Compiler oder ein Interpreter einer Programmiersprache einlesen und daraus ein Programm erzeugen oder starten kann. Anderseits sind es die Dateien, die ein Programmierer erstellt. In diesem zweiten Sinne ist ein generierter Text kein Quelltext, sondern ein Produkt eines Generators.


Icon Hinweis Quelle und Generat

Texte und Daten, die ein Programmierer erstellt und bearbeitet, werden wir als Quelle bezeichnen. Texte und Daten, die ein Generator produziert, werden wir als Generat bezeichnen.


Die Generierung ist dann unproblematisch, wenn sich die Generate von den vom Menschen programmierten Quellen klar trennen lassen.

Wenn man die Quellen und die Generate in gemeinsamen »Quelltext«-Dateien vermischt, wird es zwangsläufig passieren, dass diese Dateien bei der Änderung der Eingaben für den Generator durch die Generierung verändert werden. So werden in der Versionsverwaltung [Sie benutzen doch eine Versionsverwaltung, nicht wahr? ] Änderungen festgehalten, die nicht durch den Programmierer verursacht worden sind – Sie haben dann Schwierigkeiten, echte von generierten Änderungen zu unterscheiden.

Eine Möglichkeit, um dies zum Beispiel in C++ zu erreichen, besteht darin, die Generate in separate Dateien zu speichern und diese durch das Pragma #include in die Quelltexte einzubinden. In Ruby oder C# können Sie zum Beispiel die partiellen Klassen nutzen und die generierten Teile der Klassen in separate Quelltextdateien auslagern.

Wenn dies nicht möglich sein sollte, ist es wichtig, dafür zu sorgen, dass die generierten Teile die von Menschen erstellten Quellen nicht zerstören. Es gibt verschiedene Strategien, die das verhindern sollen.

Kombination mit generiertem Code

  • In den generierten Quelltextdateien werden spezielle geschützte Bereiche markiert, die der Generator nicht ändert. Dies funktioniert manchmal, ist aber nicht besonders schön.
  • Man bearbeitet die generierten Dateien gar nicht manuell, sondern nur deren Kopien. Die Änderungen, die sich in den generierten Dateien durch die Neugenerierung ergeben haben, werden in die manuell bearbeiteten Dateien automatisch oder manuell überführt. Genauso wie man Patches eines Originalsystems in ein modifiziertes System übernimmt.

Die Quelltextgenerierung stellt also einen nützlichen und auch praxisrelevanten Ansatz dar, um Informationen über Programme zu verwalten und diese Programme selbst wieder als Daten betrachten zu können. Die praktische Umsetzung dieses Ansatzes ist jedoch immer mit Zusatzaufwand verbunden und beinhaltet zusätzliche mögliche Fehlerquellen. In Abbildung 9.2 ist dargestellt, wie Quelltexte mit generierten Anteilen aussehen können.

Abbildung 9.2    Gemischte Quelltexte durch generierte Anteile

Im folgenden Abschnitt werden wir Fälle betrachten, bei denen Sie ohne Generierung auskommen können, indem Sie die bereits vorhandene Strukturinformation eines Programms ausnutzen.

Verwendung von Metainformationen

Im vorherigen Abschnitt haben wir SQL-Anweisungen betrachtet, die sich automatisch aus der Abbildung der Klassen auf die Datenbanktabellen und von Attributen auf die Datenbankspalten ergeben. Doch auch diese Abbildung selbst kann redundant sein.

Es kann zum Beispiel sinnvoll sein, dass die Tabellen genauso heißen wie die Klassen, deren Exemplare in ihnen gespeichert werden. Die Spalten können in diesem Fall so heißen wie die Attribute dieser Exemplare. Warum sollte man also extra spezifizieren müssen, dass das Attribut Name der Klasse Person in der Spalte Name der Tabelle Person gespeichert werden soll?

Meta- informationen

Programme verarbeiten Informationen. So kann unser Programm die Information speichern, dass der Name einer Person »Ellsworth Toohey« lautet. Die übergeordnete Information, dass eine Person überhaupt einen Namen hat, gehört zu der Struktur des Programms. Solche übergeordneten Informationen nennt man Metainformationen.


Icon Hinweis Metainformationen

Metainformationen sind Informationen über die Struktur eines Programms selbst: Welche Klassen existieren? Was sind deren Unter- und Oberklassen? Welche Attribute haben die Exemplare dieser Klassen, welche Operationen unterstützen sie, welche Methoden implementieren sie. Dass ein Objekt ein Attribut Name hat, kann zum Beispiel durch seine Zugehörigkeit zu der Klasse Person bestimmt sein.

Steht diese Metainformation auch zur Laufzeit eines Programms zur Verfügung, können darüber zum Beispiel Abbildungsregeln zwischen der Struktur von Klassen und Tabellen einer Datenbank definiert werden.


Wenn die Klassen, Methoden und andere zur Struktur eines objektorientierten Programms gehörende Elemente einfach als Objekte behandelt werden, stehen diese automatisch zur Laufzeit eines Programms zur Verfügung. Solche Objekte werden Metaobjekte genannt.


Icon Hinweis Metaobjekte

In manchen Programmiersprachen sind die Elemente selbst auch Objekte, welche die Struktur eines Programms bestimmen. So können Klassen und Methoden selbst Objekte sein, deren Eigenschaften erfragt und möglicherweise modifiziert werden können. Diese zur Programmstruktur gehörenden Elemente werden als Metaobjekte bezeichnet.

Ruby und Smalltalk sind Beispiele für Programmiersprachen, in denen Klassen vollwertige Objekte sind. In Java stehen Klassen auch als Objekte zur Verfügung, allerdings sind diese Objekte in Java nicht veränderbar.


Ein Generator, der eine Klasse auf die Struktur einer Datenbanktabelle abbilden soll, benötigt also Zugriff auf die Metainformation eines Programms. Woher soll er wissen, dass die Klasse Person ein Attribut Name hat? Diese Information steckt in den Quelltexten unserer Klassen. Der Compiler oder ein Interpreter unserer Programmiersprache bekommt sie doch ebenfalls aus den Quelltexten.

Um einen schlaueren Generator schreiben zu können, benötigen Sie also den Zugriff auf die Metainformationen Ihres Programms. Entweder müssen Sie selbst einen Parser für die genutzte Programmiersprache schreiben, oder Sie nutzen die Mittel der jeweiligen Programmiersprache, falls diese so nett ist und Ihnen den Zugriff auf die Metainformationen zur Laufzeit eines Programms ermöglicht. In diesem Fall können Sie vielleicht sogar auf den Generator verzichten.

Anstatt generierte »Quelltexte« bereitzustellen, können Sie die Abbildung auf eine relationale Datenbank zur Laufzeit mit Hilfe der Metainformationen vornehmen. Diesen Weg geht zum Beispiel Hibernate, ein frei verfügbares Framework zur Abbildung von Objekten auf relationale Datenbanken (Object Relational Mapping Tool). Hibernate verwendet die sogenannte Reflexion, um zur Laufzeit eines Programms Informationen über die Struktur von Objekten und der zugehörigen Klassen zu ermitteln.

Introspektion und Reflexion

Informationen über die Struktur des Programms nutzt ein Compiler, um Typüberprüfungen vorzunehmen und die syntaktische Korrektheit eines Programms zu prüfen.

Aber zur Laufzeit eines Programms ist diese Information nicht immer vorhanden. Bei der Übersetzung eines C++-Programms werden zum Beispiel alle Zugriffe auf das Attribut Name der Exemplare der Klasse Person durch entsprechende Zeigerarithmetik ersetzt, das laufende Programm muss nicht wissen, dass es die Namen von Personen speichert, wichtig ist nur, dass die richtigen Bytes an der richtigen Stellen gespeichert werden und dass die Aufrufstellen die richtigen Adressen anspringen. Bei der Übersetzung eines C++-Programms geht also ein großer Teil der Metainformation verloren.

In anderen objektorientierten Programmiersprachen ist diese Information aber auch zur Laufzeit verfügbar. Beispiele für diese Sprachen sind Smalltalk, Python, Ruby und mit Einschränkungen auch Java.

Die Möglichkeit, auf diese Art von Information beim Programmablauf zuzugreifen, wird als Reflexion bezeichnet.


Icon Hinweis Reflexion (engl. Reflection)

Reflexion ist ein Vorgang, bei dem ein Programm auf Informationen zugreift, die nicht zu den Daten des Programms, sondern zur Struktur des Programms selbst gehören. Diese Informationen können dabei über eine definierte Schnittstelle ausgelesen werden. Eine Modifikation dieser Strukturen ist über Reflexion nicht möglich. Die über Reflexion erhaltene Information wird auch als Metainformation bezeichnet, da es sich dabei um Informationen über das laufende Programm handelt.


Introspektion

Sehr nahe verwandt mit der Reflexion ist die Introspektion. Bei Introspektion wird in der Regel Information zusätzlich zu einer Komponente bereitgestellt. So kann man zum Beispiel die Metainformationen, die Java über eine Klasse bereitstellt, für Java Beans [Java Beans sind gewöhnliche Java-Klassen, die sich an bestimmte Konventionen halten und damit ermöglichen, dass bestimmte Tools deren Exemplare generisch bearbeiten können. So kann man zum Beispiel einen Dialogeditor schreiben, der auch mit Elementen arbeiten kann, die dem Entwickler des Editors nicht bekannt waren. Zumindest Teilen der Namenskonventionen folgt man heutzutage in verschiedenen Bereichen von Java, auch wenn es gar nicht um die visuelle Bearbeitung von Komponenten geht. ] erweitern, indem man einer Bean-Klasse X eine Hilfsklasse XBeanInfo zur Seite stellt.

Diskussion: Änderungen durch Reflexion

Gregor: Über die Reflexion in Java kann ich aber Informationen nicht nur lesen, ich kann sie auch verändern.

Bernhard: Ja, man kann die Daten des Programmes ändern, nicht aber seine Struktur. In Java kann man zum Beispiel über Reflexion keine neuen Methoden einer Klasse hinzufügen oder neue Klassen erstellen. Man kann jedoch die Werte der Attribute eines Objekts ändern oder seine Methoden aufrufen.

Gregor: Und man kann dynamische Proxy-Klassen erstellen.

Bernhard: Ja, das kann man in der Tat. Allerdings nur für vorher definierte und zur Laufzeit mit Reflexion nicht mehr änderbare Schnittstellen.

Der Zugriff auf Metainformation kann dabei unterstützen, wenn ein Anliegen wie die Persistenz von Objekten automatisiert erledigt werden soll. Für manche Arten von Anliegen reicht diese Art des Zugriffs aber nicht aus.

Wir stellen im folgenden Abschnitt 9.2 die Technik der aspektorientierten Programmierung vor, die für eine ganze Reihe von übergreifenden Anliegen Lösungsmöglichkeiten bereitstellt. Die Fähigkeiten der Aspektorientierung gehen dabei über die Möglichkeiten von Reflexion hinaus und erlauben uns, an definierten Stelle in die Struktur eines Programms einzugreifen.



Ihre Meinung

Wie hat Ihnen das Openbook gefallen? Wir freuen uns immer über Ihre Rückmeldung. Schreiben Sie uns gerne Ihr Feedback als E-Mail an kommunikation@rheinwerk-verlag.de.

 <<   zurück
  Zum Rheinwerk-Shop
Neuauflage: Objektorientierte Programmierung






Neuauflage:
Objektorientierte Programmierung

Jetzt Buch bestellen


 Ihre Meinung?
Wie hat Ihnen das Openbook gefallen?
Ihre Meinung

 Buchempfehlungen
Zum Rheinwerk-Shop: Java ist auch eine Insel






 Java ist auch
 eine Insel


Zum Rheinwerk-Shop: Schrödinger programmiert C++






 Schrödinger
 programmiert C++


Zum Rheinwerk-Shop: C++ Handbuch






 C++ Handbuch


Zum Rheinwerk-Shop: Einstieg in Python






 Einstieg in Python


Zum Rheinwerk-Shop: IT-Handbuch für Fachinformatiker






 IT-Handbuch für
 Fachinformatiker


 Lieferung
Versandkostenfrei bestellen in Deutschland, Österreich und der Schweiz
InfoInfo




Copyright © Rheinwerk Verlag GmbH 2009
Für Ihren privaten Gebrauch dürfen Sie die Online-Version natürlich ausdrucken. Ansonsten unterliegt das Openbook denselben Bestimmungen, wie die gebundene Ausgabe: Das Werk einschließlich aller seiner Teile ist urheberrechtlich geschützt.
Alle Rechte vorbehalten einschließlich der Vervielfältigung, Übersetzung, Mikroverfilmung sowie Einspeicherung und Verarbeitung in elektronischen Systemen.


Nutzungsbestimmungen | Datenschutz | Impressum

Rheinwerk Verlag GmbH, Rheinwerkallee 4, 53227 Bonn, Tel.: 0228.42150.0, Fax 0228.42150.77, service@rheinwerk-verlag.de

Cookie-Einstellungen ändern