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:
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).
|