Prinzipien der Objektorientierung Problem bei der Softwareentwicklung: Die Welt ist sehr komplex Lösungsansatz: - Problem auf das Wesentliche reduzieren - Modularisierung - strukturierte Einordnung (Hierarchie) - Kapselung zusammengehöriger Informationen Die Objektorientierung (OO) versucht eine Methode anzubieten, die dem menschlichen Denken nachempfunden ist. Daraus ergeben sich folgende Prinzipien der OO: - Abstraktion - Kapselung - Vererbung - Polymorphismus (als Funktionsweise innerhalb der Vererbung) Abstraktion Der Mensch bedient sich des Prinzips Abstraktion, um mit Komplexität umgehen zu können. Abstraktion bedeutet, sich auf das Wesentliche zu konzentrieren, also Unwichtiges außer acht zu lassen, und Gemeinsamkeiten zwischen verschiedenen Objekten zu erkennen. Die (Um)welt ist von sich aus komplex. Um sie einfacher verstehen zu können, werden diejenigen Informationen bewußt ausgewählt, die benötigt werden, um einen Sachverhalt verständlich zu machen. Dabei wird in Kauf genommen (und sogar absichtlich darauf abgezielt), daß ein Teil der Realität nicht berücksichtigt wird. Durch die Anwendung von Abstraktion erhalten wir also ein Modell der Realität. Dadurch werden Objekte identifiziert, deren Beziehungen und Abhängigkeiten, sowie deren Verhalten. Das Resultat ist eine Klassenhierarchie. Schwierigkeit: Grad der Abstraktion entsprechend dem Anwendungsfall geeignet zu wählen. Eine Klasse von Objekten wird beschrieben durch - Beschreibung der Vorgehensweise zum Erzeugen einer neuen Instanz (Konstruktor) - gemeinsame Attribute - gemeinsame (interne) Verhaltensweisen (private Methoden) - gemeinsame Reaktionen (Verhaltensweisen nach Nachrichtenempfang, öffentliche Methoden) Hierarchiebildung Beziehungen und Abhängigkeiten zwischen Objekten bilden Klassenhierarchien. Diese dienen dem Problemverständnis, denn je weiter „oben“ in der Hierarchie ein Objekt einzuordnen ist, desto allgemeiner ist es (Generalisierung). Dagegen ist ein Objekt weiter „unten“ immer eine Spezialisierung. Kapselung Durch das Prinzip Kapselung wird jedes Objekt zu einer abgeschlossenen Einheit, die über definierte Schnittstellen mit der Umwelt kommunizieren kann (Beispiel: Black Box mit Knöpfen, Schaltern und Anzeigefeldern). Diese Form der Modularisierung hilft dabei, Problemlösungen durch Lösung von Teilproblemen zu erzielen; dies bedeutet also eine Vereinfachung des Lösungsverfahrens. Kapselung bedeutet, daß Attribute (Eigenschaften) und Methoden (Verhaltensweisen) in einem Objekt zusammengefaßt werden. Der interne Aufbau und das interne Verhalten eines Objektes (Innensicht) werden nach außen nicht gezeigt (Geheimnisprinzip, information hiding). Von außen sind nur die zugehörigen Methodennamen eines Objektes sichtbar, aber nicht ihre konkrete Implementation (Programmcode), d.h. ein Objekt kann nur über Botschaften (Nachrichten) die Methoden eines anderen Objektes über klar definierte Schnittstellen auslösen, ohne die internen Implementationsdetails zu kennen (Außensicht). Unter Umständen beinhaltet ein Objekt eine Reihe von Attributen und Methoden, die nur intern benötigt werden (private Methoden) und deshalb von außen (d.h. für andere Objekte) nicht verfügbar sind. Dies verhindert z.B., daß interne Methoden und Attribute versehentlich geändert würden, was zu Fehlern führen könnte. Ein Vorteil der Kapselung von Attributen und Methoden ist, daß nur erlaubte Zugriffe möglich sind. Bei der Arbeit mit einem Objekt ist somit ein unbeabsichtigter Zugriff auf interne Informationen nicht möglich, da für diese Daten keine Schnittstelle nach außen definiert ist. Des weiteren ermöglicht das Prinzip der Kapselung, daß die interne Struktur eines Objektes beliebig verändert werden kann, ohne daß Programme, die dieses Objekt nutzen, umgestellt werden müssen, denn diese bedienen sich ausschließlich der öffentlichen Schnittstellen. Anders ausgedrückt ist die Verwendung eines Objektes unabhängig von dessen Implementierung. Zusammenfassung Kapselung: - Attribute und Methoden werden in einem Objekt zusammengefaßt Programmcode ist nach außen nicht sichtbar Kommunikation nur über definierte Schnittstellen Lese-/Schreibzugriff auf Attribute wird dadurch kontrolliert Implementation kann geändert werden, sofern Schnittstellen beibehalten werden Vererbung Bei der Betrachtung von Klassenhierarchien werden alle Vorgänger einer Klasse als Oberklassen (super classes, Superklassen) und alle Nachfolger als Unterklassen (sub classes, Subklassen) bezeichnet. Eine Klasse erbt alle Eigenschaften der unmittelbaren Oberklasse und gibt all ihre Eigenschaften an die unmittelbare Unterklasse weiter. Geerbte Eigenschaften werden dabei ebenfalls weitergegeben. Dieser Vorgang wird als Vererbung bezeichnet. Mit Eigenschaften sind dabei Methoden und Attribute der jeweils betrachteten Klasse gemeint. In Java besitzt eine Klasse genau eine unmittelbare Oberklasse (mit Ausnahme der Klasse Object, die den Kopf jeder Klassenhierarchie in Java darstellt). Ist keine konkrete Oberklasse angegeben, wird standardmäßig Object als Oberklasse festgelegt. Durch diese Strukturierung lassen sich eindeutige Pfade innerhalb einer Klassenhierarchie bilden. Jede Ebene in der Hierarchie stellt eine Abstraktionsebene dar. Im Hinblick auf Wiederverwendbarkeit ist es wichtig, daß eine Klasse der jeweiligen Hierarchieebene nur Methoden und Attribute enthält, die diese Ebene allgemein beschreiben. Wie erstelle ich eine Subklasse? Zu einer Klasse kann eine Oberklasse angegeben werden. Dies geschieht durch das Schlüsselwort extends. Beispiel: public class Auto extends Fortbewegungsmittel { Was geschieht mit den Konstruktoren? Wird in einer Subklasse kein Konstruktor implementiert, wird der Default-Konstruktor der Subklasse ausgeführt, der seinerseits den parameterlosen Konstruktor der Oberklasse aufruft. Wird in einer Subklasse mindestens ein Konstruktor implementiert, werden alle Konstruktoren aus der Oberklasse überschrieben. Was geschieht mit den Attributen? Alle Attribute, die nicht als private in der Oberklasse deklariert sind, werden vererbt. D.h. man kann direkt auf sie zugreifen. Vererbte Attribute können „überschrieben“ werden, indem Attribute gleichen Namens in der Subklasse deklariert werden. Was geschieht mit den Methoden? Alle Methode, die nicht als private in der Oberklasse deklariert sind, werden vererbt. D.h. man kann direkt auf sie zugreifen. Vererbte Methoden können „überschrieben“ werden, indem Methoden gleicher Signatur (d.h. auch gleichen Namens) deklariert werden. Methoden gleichen Namens mit anders lautender Signatur bestehen danach weiterhin (s. MSKonto.java aus der Vorlesung). Polymorphismus (Teil 1) Zur menschlichen Denkweise gehört es, kontextabhängig zu denken, Nachrichten also entsprechend ihrem jeweiligen Kontext einzuordnen. In der OO, die versucht, die menschliche Denkweise abzubilden, können Methoden kontextabhängig verwendet werden. Das wird Polymorphismus genannt. Im Rahmen der Vererbung werden „kontextabhängige Methoden“ weit oben in der Klassenhierarchie angesiedelt, oft in so genannten „abstrakten Klassen“. Die konkrete Ausprägung einer Methode wird dann weiter unten in den jeweiligen konkreten Klassen individuell realisiert (durch „Überladen“ der Methoden). Nehmen wir als Beispiel eine Oberklasse Fortbewegungsmittel, die eine Methode fahre() besitzt. Fortbewegungsmittel hat die Unterklassen Auto, Flugzeug und Schiff. Jede dieser Klassen implementiert die Methode fahre(). Trifft die Nachricht „fahre!“ in einem dieser Objekte ein (fahre() wird aufgerufen), reagiert jedes Objekt unterschiedlich, nämlich gemäß seinem Kontext: das Auto fährt auf dem Land, das Flugzeug fliegt in der Luft, und das Schiff bewegt sich auf dem Wasser. In Java existieren zwei Ausprägungen von Polymorphismus: Statisches Binden (early binding, frühes Binden) und Dynamisches Binden (late binding, spätes Binden) Statisches Binden: Einfache Form von Polymorphismus, wird auch als Overloading (Überladen) bezeichnet. Dabei haben verschiedene Methoden den gleichen Namen und den gleichen Rückgabewert, aber unterschiedliche Signaturen. Dabei ist es unerheblich, ob diese Methoden innerhalb einer Klasse oder innerhalb einer Klassenhierarchie implementiert sind. Eine (Methoden-)Signatur besteht aus drei Bestandteilen: - Rückgabewert - Methodenname - Datentypen der Argumentliste Manchmal wird allein der dritte Teil als Signatur bezeichnet. Beispiele: public void dummy(String s, int i) hat die Signatur void dummy(String, int) public void dummy(String s, int j) hat die Signatur void dummy(String, int) public void dummy(int i, String s) hat die Signatur void dummy(int, String) public void dummy(int j, String s, int i) hat die Signatur void dummy(int, String, int) Einfach gesagt erhält man die Signatur einer Methode aus dem Implementationskopf, aus dem Zugriffsbezeichner (public, protected, private) und Argumentbezeichner entfernt werden. Warum heißt es statisches oder frühes Binden? Weil schon beim Kompilieren offensichtlich ist, welche Methode gemeint ist (durch entsprechenden Methodenaufruf, d.h. die Argumentliste der Methode, bzw. die Signatur dieser Methode). Die Entscheidung fällt also zur „Build Time“. Dynamisches Binden: Wird auch als Overriding (ungenau übersetzt „Überschreiben“) bezeichnet. Tritt auf, wenn in einer Unterklasse eine Methode implementiert wird, die dieselbe Signatur (also auch denselben Namen) besitzt wie eine Methode einer übergeordneten Klasse. Im Gegensatz zum statischen Binden kann hier erst zur Laufzeit („Runtime“) entschieden werden, welche Methode aufgerufen werden soll, denn das ist abhängig davon, in welchem Objekt diese Methode angesprochen wird. Wir werden diese Form von Polymorphismus genauer im zweiten Teil betrachten.