OOP I Grundlagen:
1. Konzepte Objektorientierter Programmiersprachen
Einführung: Die objektorientierte Programmierung war für die Softwareindustrie eine der wichtigsten Errungenschaften der 90er Jahre. Sie führte zu fehlerärmeren und besser wartbaren Programmen. Das Konzept der OOP ist in allen Programmiersprachen bis auf kleine Details das Selbe. Aus diesem Grund ist mein Referat im Großen und Ganzen eine Wiederholung aus dem Vorjahr. Es gibt allerdings einige Unterschiede zwischen C und Java über die man informiert sein sollte.
1.1 Abstraktion: Eine der wichtigsten Ideen der OOP ist die Trennung zwischen Konzept und Umsetzung. Das Konzept entspricht der Klasse und die Umsetzung dem Objekt. Eine Klasse ist die Beschreibung eines oder mehrerer Objekte die sich so ähnlich sind, dass eine gemeinsame Beschreibung angebracht erscheint. Ein Objekt ist die Instanz einer Klasse im Speicher. In einer Klasse werden drei wichtige dinge beschrieben:
Wie ist das Objekt zu bedienen? (Methoden)
Welche Eigenschaften enthält das Objekt und wie verhält es sich? (Attribute)
Wie wird das Objekt hergestellt? (Konstruktor)
Diese Unterscheidung zwischen Objekten und Klassen kann als Abstraktion angesehen werden. Abstraktion hilft, Details zu ignorieren, und reduziert damit die Komplexität des Problems. Sie bildet die erste wichtige Eigenschaft objektorientierter Programmiersprachen.
1.2 Kapselung: In objektorientierten Programmiersprachen wird eine Klasse durch die Zusammenfassung einer Menge von Daten und darauf operierender Methoden definiert. Die Daten werden durch Variablen repräsentiert, die für jedes Objekt neu angelegt werden. Die Methoden hingegen existieren nur einmal im Programmcode und bei Laufzeit wird ihnen ein Verweis auf die richtigen Variablen übergeben. Die Methoden repräsentieren das Verhalten des Objekts. Sie sind - von gewollten Ausnahmen abgesehen, bei denen Variablen bewußt von außen zugänglich gemacht werden - die einzige Möglichkeit, mit dem Objekt zu kommunizieren.
Diese Zusammenfassung von Methoden und Variablen zu Klassen bezeichnet man als Kapselung. Kapselung hilft vor allem, die Komplexität der Bedienung eines Objekts zu reduzieren. Sie vermindert aber auch die Komplexität der Implementierung, denn undefinierte Interaktionen mit anderen Bestandteilen des Programms werden verhindert oder reduziert.
1.3 Wiederverwendung:
Durch die Abstraktion und Kapselung wird die Wiederverwendung von Programmelementen gefördert, die dritte wichtige Eigenschaft objektorientierter Programmiersprachen. Programmteile die bereits getestet wurden können einfach in einem anderen Projekt verwendet werden. Wiederverwendung ist ein wichtiger Schlüssel zur Erhöhung der Effizienz und Fehlerfreiheit beim Programmieren.
1.4 Beziehungen: Objekte und Klassen existieren für gewöhnlich nicht völlig alleine, sondern stehen in Beziehungen zueinander. So ähnelt ein Motorrad beispielsweise einem Auto. Ein Auto hat auch mit einem Lastwagen Gemeinsamkeiten. Dieser kann wiederum einen Anhänger haben, auf dem zum Beispiel ein Motorrad steht. Diese Beziehungen sind nicht unwesentlich und müssen in einem Programm berücksichtigt werden. Es gibt drei Arten von Beziehungen:
"is-a" - Beziehung: Generalisierung und Spezialisierung
"part-of" - Beziehung: Aggregation und Komposition
Verwendungs- und Aufrufbeziehungen
Generalisierung und Spezialisierung: Zuerst wollen wir die 'ist ein' Beziehung behandeln. Diese beschreibt Beziehungen zwischen 'ähnlichen' Klassen. Ein Fahrrad ist zum Beispiel ein Zweirad.
Die 'is-a'-Beziehung zwischen zwei Klassen A und B sagt aus, daß 'B ein A ist', also alle Eigenschaften von A besitzt, und vermutlich noch ein paar mehr. B ist demnach eine Spezialisierung von A. Andersherum betrachtet, ist A eine Generalisierung (Verallgemeinerung) von B.
Diese Beziehungen werden durch Vererbung ausgedrückt. Eine Klasse wird dabei nicht komplett neu definiert, sondern von einer anderen Klasse abgeleitet. In diesem Fall erbt sie alle Eigenschaften dieser Klasse und kann nach Belieben eigene hinzufügen.
Vererbungen können mehrstufig sein, d.h. eine abgeleitete Klasse kann Basisklasse für weitere Klassen sein. Auf diese Weise können vielstufige Vererbungshierarchien entstehen. Vererbungshierarchien werden wegen ihrer Baumstruktur auch als Ableitungsbäume bezeichnet. Sie werden meist durch Graphen dargestellt, in denen die abgeleiteten Klassen durch Pfeile mit den Basisklassen verbunden sind und die Basisklassen oberhalb der abgeleiteten Klassen stehen. Für unsere Fahrzeugwelt ergäbe sich beispielsweise folgender Ableitungsbaum: <Overheadfolie: Vererbungshirachie>
Als Eigenschaften der Basisklasse Transportmittel könnten etwa dessen Anschaffungskosten, seine Lebensdauer oder die Transportgeschwindigkeit angesehen werden. Sie gelten für alle abgeleiteten Klassen. In der zweiten Ableitungsebene wird nach der Art der Fortbewegung unterschieden.
Es muss somit beim Klassendesign entschieden werden welche Eigenschaften welche Klasse haben soll damit diese nicht zu komplex wird und dass die Klasse trotzdem alle wichtigen Eigenschaften und Methoden enthält.
Aggregation und Komposition: Der zweite Beziehungstyp, die 'part-of' - Beziehungen, beschreibt die Zusammensetzung eines Objekts aus anderen Objekten (dies wird auch als Komposition bezeichnet). So besteht beispielsweise der Güterzug aus einer Lokomotive und mehreren Güterzuganhängern. Die "ist Teil von" - Beziehung muss nicht zwangsläufig beschreiben, woraus ein Objekt zusammengesetzt ist. Sie kann auch den allgemeineren Fall des einfachen Aufnehmens anderer Objekte beschreiben (was auch als Aggregation bezeichnet wird). Zum Beispiel kann mit dieser Beziehung Ausgedrückt werden, dass ein Motorad auf einem Lastwagen transportiert wird.
Diese Beziehungen werden durch Instanzvariablen ausgedrückt, die Objekt aufnehmen können.
Verwendungs- und Aufrufbeziehungen: Die dritte Art von Beziehungen zwischen Objekten oder Klassen hat den allgemeinsten Charakter. Benutzt beispielsweise eine Methode während ihrer Ausführung ein temporäres Objekt, so besteht zwischen beiden eine Verwendungsbeziehung: Objekt X verwendet eine Instanz der Klasse Y, um bestimmte Operationen auszuführen. Allgemeine Verwendungs- oder Aufrufbeziehungen finden in OOP ihren Niederschlag darin, daß Objekte als lokale Variablen oder Methodenargumente verwendet werden. Sie werden auch mit dem Begriff Assoziationen bezeichnet.
1.5 Polymorphismus: Als letztes wichtiges Konzept objektorientierter Programmiersprachen wollen wir uns mit dem Polymorphismus beschäftigen. Polymorphismus bedeutet direkt übersetzt etwa 'Vielgestaltigkeit' und bezeichnet zunächst einmal die Fähigkeit von Objektvariablen, Objekte unterschiedlicher Klassen aufzunehmen. Das beschränkt sichallerdings für eine Objektvariable des Typs X auf alle Objekte der Klasse X oder einer daraus abgeleiteten Klasse.
Eine Objektvariable vom Typ Straßenfahrzeug kann also nicht nur Objekte der Klasse Straßenfahrzeug aufnehmen, sondern auch Objekte der Klassen Zweirad, Vierrad, Anhänger, Motorrad, Fahrrad, Auto und Lastwagen. Dies entspricht genau dem gewohnten Umgang mit Vererbungsbeziehungen. Ein Zweirad ist nunmal ein Straßenfahrzeug, hat alle Eigenschaften eines Straßenfahrzeugs und kann daher durch eine Variable repräsentiert werden, die auf ein Straßenfahrzeug verweist. Daß es möglicherweise ein paar zusätzliche Eigenschaften besitzt, stört den Compiler nicht. Er hat nur sicherzustellen, daß die Eigenschaften eines Straßenfahrzeugs vollständig vorhanden sind.
Interessant wird Polymorphismus, wenn die Programmiersprache zusätzlich das Konzept des Late Binding implementiert. Im Unterschied zum 'Early Binding' wird dabei nicht bereits zur Compilezeit entschieden, welche Ausprägung einer bestimmten Methode aufgerufen werden soll, sondern erst zur Laufzeit. Wenn beispielsweise auf einem Objekt eine Methode aufgerufen werden soll, ist zwar prinzipiell bereits zur Compilezeit klar welche das ist. Objektorientierte Programmiersprachen erlauben aber das Überlagern von Methoden in abgeleiteten Klassen und es könnte f in einer dieser nachgelagerten Klassen überlagert worden sein. Welche konkrete Methode dann aufgerufen werden muß, kann damit erst zur Laufzeit entschieden werden.
Nun ist dieses Verhalten keinesfalls hinderlich oder unerwünscht, sondern kann zu typbasierten Fallunterscheidungen genutzt werden Betrachten wir dazu noch einmal unsere Hierarchie von Transportmitteln. Angenommen, unser Unternehmen verfügt über einen breit gefächerten Fuhrpark von Transportmitteln. Als Unternehmer interessieren uns natürlich die Kosten jedes Transportmittels pro Monat, und wir würden dazu eine Methode getMonatsKosten in der Basisklasse Transportmittel definieren. Ganz offensichtlich läßt sich diese dort aber nicht implementieren, denn beispielsweise gestaltet sich die Berechnung der monatlichen Kosten unseres Fährschiffes ungleich schwieriger als die der Fahrräder.
Anstatt nun in aufwendigen Fallunterscheidungen für jedes Objekt zu prüfen, von welchem Typ es ist, muß lediglich diese Methode in jeder abgeleiteten Klasse implementiert werden. Besitzt das Programm etwa ein Array von Transportmittel-Objekten, kann dieses einfach durchlaufen und für jedes Element getMonatsKosten aufgerufen werden. Das Laufzeitsystem kennt den jeweiligen konkreten Typ und kann die korrekte Methode aufrufen (und das ist die aus der eigenen Klasse, nicht die in Transportmittel definierte).
2. Klassen und Objekte
2.1 Klassen: Eine Klasse wird in Java durch das Schlüsselwort class eingeleitet. In den geschweiften Klammern folgen nun die Attribute und die Methoden die im Gegensatz zu C++ inline definiert werden müssen, da es keine Headerfiles mehr gibt.
<Overheadfolie: Klassendefinition>
2.2 Objekte: Um von einer Klasse ein Objekt anzulegen, muß eine Variable vom Typ der Klasse deklariert und ihr mit Hilfe des new-Operators ein neu erzeugtes Objekt zugewiesen werden.
<Overheadfolie: Instanzierung eines Objekts>
Die erste Anweisung ist eine normale Variablendeklaration. Anstelle eines primitiven Typs wird hier der Typname einer zuvor definierten Klasse verwendet. Im Unterschied zu primitiven Datentypen wird die Objektvariable meinKombi als Referenz gespeichert. Die zweite Anweisung generiert mit Hilfe des new-Operators eine neue Instanz der Klasse Auto und weist sie der Variablen meinKombi zu.
In Java wird jede selbstdefinierte Klasse mit Hilfe des new-Operators instanziert. Mit Ausnahme von Strings und Arrays, bei denen der Compiler auch Literale zur Objekterzeugung zur Verfügung stellt, gilt dies auch für alle vordefinierten Klassen der Java-Klassenbibliothek.
Nach der Initialisierung haben alle Variablen des Objekts zunächst Standardwerte. Um die Variablen des Objekts zu verändern wird mittels Punktnotation darauf zugegriffen. Der Lesezugriff erfolgt ebenfalls auf diese Weise.
3. Methoden:
3.1 Definition: Methoden definieren das Verhalten von Objekten. Sie werden innerhalb einer Klassendefinition angelegt und haben Zugriff auf alle Variablen des Objekts. Methoden sind das Pendant zu den Funktionen anderer Programmiersprachen, arbeiten aber immer mit den Variablen des aktuellen Objekts. Globale Funktionen, die vollkommen unabhängig von einem Objekt oder einer Klasse existieren, gibt es in Java ebensowenig wie globale Variablen. Es gibt allerdings Klassenvariablen und Methoden, die nicht an eine konkrete Instanz gebunden sind.
Die Syntax der Methodendefinition in Java ähnelt der von C/C++:
Typ Name ([Parameter])
Nach einer Reihe von Modifiern (Private, ) folgen der Typ des Rückgabewerts der Funktion, ihr Name und eine optionale Parameterliste. In geschweiften Klammern folgt dann der Methodenrumpf, also die Liste der Anweisungen, die das Verhalten der Methode festlegen.
<Overheadfolie: Klassendefinition mit Methoden>
3.2 Aufruf: Der Aufruf einer Methode erfolgt wie beim Zugriff auf ein Attribut mit der Punktnotation. Wie in C++ gibt die Referenzvariable this diese wird auch mit einem Punkt angesprochen.
3.3 Parameter: Parameter werden in Java genau wie in C++ verwendet. Alle Parameter werden per call by value übergeben. Beim Aufruf einer Methode wird also der aktuelle Wert in die Parametervariable kopiert und an die Methode übergeben. Veränderungen der Parametervariablen innerhalb der Methode bleiben lokal und wirken sich nicht auf den Aufrufer aus.
Objektvariablen sind Referenzen, also Zeiger. Zwar werden auch sie bei der Übergabe an eine Methode per Wert übergeben. Da innerhalb der Methode aber der Zeiger auf das Originalobjekt zur Verfügung steht, wirken sich Veränderungen an dem Objekt natürlich direkt auf das Originalobjekt aus und sind somit für den Aufrufer der Methode sichtbar. Die Übergabe von Objekten an Methoden hat damit zwei wichtige Konsequenzen:
Die Methode erhält keine Kopie, sondern arbeitet mit dem Originalobjekt.
Die Übergabe von Objekten ist performant, gleichgültig wie groß sie sind.
Um Objekte zu kopieren muß man die Methode clone der Klasse Object aufrufen.
3.4 Rückgabewert: In Java können wie in C++ Rückgabewerte verwendet werden. Diese können sowohl normale Variablen als auch Objekte sein. Der Ausdruck wird mit der return-Anweisung zurückgegeben. Der Java-Compiler sorgt mit einer Datenflussanalyse dafür, dass hinter der return-Anweisung keine unerreichbaren Anweisungen stehen und daß jeder mögliche Ausgang einer Funktion mit einem return versehen ist. Der in C beliebte Fehler, einen Funktionsausgang ohne return-Anweisung zu erzeugen ist also nicht möglich
3.5 Überladen von Methoden: In Java ist es erlaubt, Methoden zu überladen, d.h. innerhalb einer Klasse zwei unterschiedliche Methoden mit demselben Namen zu definieren. Der Compiler unterscheidet die verschiedenen Varianten anhand der Anzahl und der Typisierung ihrer Parameter. Haben zwei Methoden denselben Namen, aber unterschiedliche Parameterlisten, werden sie als verschieden angesehen. Es ist dagegen nicht erlaubt, zwei Methoden mit exakt demselben Namen und identischer Parameterliste zu definieren. Der Rückgabetyp einer Methode trägt nicht zu ihrer Unterscheidung bei.
Das Überladen von Methoden ist dann sinnvoll, wenn die gleichnamigen Methoden auch eine vergleichbare Funktionalität haben. Eine typische Anwendung von überladenen Methoden besteht in der Simulation von variablen Parameterlisten. Auch, um eine Funktion, die bereits an vielen verschiedenen Stellen im Programm aufgerufen wird, um einen weiteren Parameter zu erweitern, ist es nützlich, diese Funktion zu überladen, um nicht alle Aufrufstellen anpassen zu müssen.
3.6 Konstruktoren: <Overheadfolie Konstruktoren>
In jeder objektorientierten Programmiersprache lassen sich spezielle Methoden definieren, die bei der Initialisierung eines Objekts aufgerufen werden: die Konstruktoren. In Java werden Konstruktoren als Methoden ohne Rückgabewert definiert, die den Namen der Klasse erhalten, zu der sie gehören. Konstruktoren dürfen eine beliebige Anzahl an Parametern haben und können überladen werden.
Soll ein Objekt unter Verwendung eines parametrisierten Konstruktors instanziert werden, so sind die Argumente wie bei einem Methodenaufruf in Klammern nach dem Namen des Konstruktors anzugeben.
Default-Konstruktoren:
Falls eine Klasse überhaupt keinen expliziten Konstruktor besitzt, wird vom Compiler automatisch ein parameterloser default-Konstruktor generiert. Enthält eine Klassendeklaration dagegen nur parametrisierte Konstruktoren, wird kein default-Konstruktor erzeugt, und die Klassendatei besitzt überhaupt keinen parameterlosen Konstruktor.
Unterschiedliche Konstruktoren einer Klasse können in Java verkettet werden, d.h. sie können sich gegenseitig aufrufen. Der aufzurufende Konstruktor wird dabei als eine normale Methode angesehen, die über den Namen this aufgerufen werden kann. Die Unterscheidung zum bereits vorgestellten this-Pointer nimmt der Compiler anhand der runden Klammern vor, die dem Aufruf folgen.
Der Vorteil der Konstruktorenverkettung besteht darin, daß vorhandener Code wiederverwendet wird. Führt ein parameterloser Konstruktor eine Reihe von nichttrivialen Aktionen durch, so ist es natürlich sinnvoller, diesen in einem spezialisierteren Konstruktor durch Aufruf wiederzuverwenden, als den Code zu duplizieren.
Wird ein Konstruktor in einem anderen Konstruktor derselben Klasse explizit aufgerufen, muß dies als erste Anweisung innerhalb der Methode geschehen. Steht der Aufruf nicht an erster Stelle, gibt es einen Compiler - Fehler.
Initialisierungsreihenfolge:
Die Initialisierung der Konstruktoren wird in der Vererbungshierarchie von oben nach unten ausgeführt.
3.7 Destruktoren: Neben Konstruktoren, die während der Initialisierung eines Objekts aufgerufen werden, gibt es in Java auch Destruktoren. Sie werden unmittelbar vor dem Zerstören eines Objekts aufgerufen.
Ein Destruktor wird als geschützte (protected) parameterlose Methode mit dem Namen finalize definiert:
Da Java über ein automatisches Speichermanagement verfügt, kommt den Destruktoren hier eine viel geringere Bedeutung zu als in anderen objektorientierten Sprachen. Anders als etwa in C++ muß sich der Entwickler ja nicht um die Rückgabe von belegtem Speicher kümmern; und das ist sicher eine der Hauptaufgaben von Destruktoren in C++.
4. Fazit:
Objektorientierte Programmierung erlaubt eine natürliche Modellierung vieler Problemstellungen. Sie vermindert die Komplexität eines Programms durch Abstraktion, Kapselung, definierte Schnittstellen und Reduzierung von Querzugriffen. Sie stellt Hilfsmittel zur Darstellung von Beziehungen zwischen Klassen und Objekten dar, und sie erhöht die Effizienz des Entwicklers durch Förderung der Wiederverwendung von Programmcode.
Haupt | Fügen Sie Referat | Kontakt | Impressum | Nutzungsbedingungen