Objektorientierung: Klassen, Objekte und Polymorphie
Bei der Objektorientierung modellierst du ein Programm als Zusammenspiel eigenständiger Objekte. Jedes Objekt besitzt eine Identität, einen Zustand und ein Verhalten. Klassen beschreiben den gemeinsamen Aufbau ähnlicher Objekte.
Auf dieser Seite lernst du, ein kleines Objektmodell zu entwerfen, gekapselte Zustandsänderungen nachzuvollziehen und polymorphe Methodenaufrufe zu erklären.
Hake ab, was du schon kannst — und komm am Ende hierher zurück!
Objekte verbinden Zustand und Verhalten
Stell dir zwei Lampen in einem Smart Home vor. Beide können ausgeschaltet sein und sehen im Programm gleich aus. Trotzdem sind es zwei verschiedene Objekte: Die Küchenlampe kann eingeschaltet werden, ohne dass sich die Flurlampe verändert.
Objekt
Ein Objekt ist eine eigenständige Programmeinheit mit Identität, Zustand und Verhalten. Es kann auch etwas Abstraktes wie ein Benutzerkonto oder eine Bestellung darstellen.
- Die Identität unterscheidet das Objekt von allen anderen Objekten.
- Der Zustand ergibt sich aus seinen aktuellen Attributwerten.
- Das Verhalten wird durch Methoden beschrieben.
Bei einer Lampe kann das Attribut an den Zustand speichern. Methoden wie einschalten() und ausschalten() verändern diesen Zustand.
Gleicher Zustand bedeutet nicht gleiche Identität. Zwei ausgeschaltete Lampen bleiben zwei verschiedene Objekte.
Von der Klasse zur konkreten Instanz
In klassenbasierten Sprachen legt eine Klasse fest, welche Attribute und Methoden ihre Objekte besitzen. Sie ist der gemeinsame Bauplan, aber noch nicht das einzelne Objekt.
Klasse
Eine Klasse beschreibt einen Objekttyp. Ein nach dieser Beschreibung erzeugtes Objekt heißt Instanz der Klasse. Das Erzeugen wird Instanziierung genannt.
Für eine Klasse Lampe könnte der gemeinsame Aufbau so aussehen:
- Attribut:
an - Konstruktor:
Lampe() - Methoden:
einschalten(),ausschalten()undistAn()
Der Konstruktor richtet ein neues Objekt ein. Er gibt ihm einen gültigen Anfangszustand. Bei der Lampe setzt der Konstruktor an auf false.
In Java erzeugt new Lampe() eine neue Instanz. Die Deklaration Lampe kueche; allein erzeugt noch keine Lampe. Sie stellt nur eine Referenzvariable bereit, die später auf ein Objekt verweisen kann.
Lampe kueche = new Lampe(); lässt sich in drei Gedanken zerlegen:
new Lampe()erzeugt ein neues Lampenobjekt.- Der Konstruktor setzt den Anfangszustand
an = false. - Die Referenzvariable
kuecheverweist anschließend auf dieses Objekt.
Ein Aufruf von kueche.einschalten() verändert den Zustand genau dieses Lampenobjekts zu an = true.
Wähle in jeder Lücke die passende Form und prüfe anschließend deine Antworten.
Eine beschreibt den gemeinsamen Aufbau. Ein konkretes Objekt dieser Klasse heißt . Der richtet dessen Anfangszustand ein.
Kapselung schützt gültige Zustände
Wenn fremder Code jedes Attribut beliebig verändern dürfte, könnte ein Objekt leicht in einen ungültigen Zustand geraten. Kapselung begrenzt deshalb den direkten Zugriff.
Kapselung
Kapselung bedeutet, Zustand und zugehöriges Verhalten in einem Objekt zu bündeln. Andere Programmteile greifen über eine festgelegte Schnittstelle darauf zu, statt interne Attribute unkontrolliert zu verändern.
Für die Lampe kann an privat sein. Außenstehender Code nutzt dann einschalten(), ausschalten() oder istAn(). Die Klasse kontrolliert selbst, wie ihr Zustand gelesen oder geändert wird.
Ein vollständiger Zustandsweg sieht so aus:
- Der Konstruktor erzeugt eine ausgeschaltete Lampe.
einschalten()setztanauftrue.istAn()liefert den aktuellen Zustand, verändert ihn aber nicht.ausschalten()setztanwieder auffalse.
Du modellierst ein Support-Ticket mit dem Zustand offen oder geschlossen. Das Attribut bleibt privat. Der Konstruktor erzeugt ein offenes Ticket. Die Methode schliessen() erlaubt den Wechsel zu geschlossen.
Eine freie Zuweisung von außen ist nicht nötig. Damit bleibt die Regel an einer Stelle gebündelt: Nur die Klasse entscheidet, wie sich der Ticketzustand ändert.
Objekte arbeiten über Nachrichten zusammen
Ein objektorientiertes Programm besteht meist nicht aus einem einzigen Objekt. Objekte fordern über Methodenaufrufe Leistungen voneinander an.
Dabei lassen sich zwei Seiten unterscheiden:
- Eine Nachricht bezeichnet den Auftrag an ein bestimmtes Empfängerobjekt, zum Beispiel
kueche.einschalten(). - Die Methode legt innerhalb der Klasse fest, wie das Empfängerobjekt den Auftrag ausführt.
Der aufrufende Programmteil muss die öffentliche Schnittstelle kennen. Er muss nicht wissen, wie die Lampe ihren Zustand intern speichert.
Ein Objekt Lichtschalter kennt eine bestimmte Lampe. Beim Betätigen sendet der Schalter dieser Lampe den Auftrag einschalten().
Der Schalter entscheidet, welches Objekt den Auftrag erhält. Die Lampe entscheidet durch ihre Methode, wie sie ihn ausführt. So bleiben die Verantwortlichkeiten getrennt.
Eine gute Objektaufteilung beantwortet zwei Fragen klar: Welches Objekt ist für eine Aufgabe zuständig, und welche Schnittstelle bietet es dafür an?
Vererbung und Polymorphie gezielt einsetzen
Mehrere Klassen können denselben fachlichen Vertrag erfüllen. Vererbung oder eine gemeinsame Schnittstelle sind dann sinnvoll, wenn Objekte tatsächlich austauschbar verwendet werden sollen.
Vererbung
Bei der Vererbung übernimmt eine Subklasse Bestandteile einer Superklasse und kann sie ergänzen oder geeignete Methoden überschreiben. Sie modelliert eine fachliche Ist-ein-Beziehung.
EMailMeldung und SMSMeldung können beispielsweise den gemeinsamen Vertrag Meldung mit der Methode senden() erfüllen. Jede Klasse setzt das Senden anders um.
Polymorphie
Polymorphie bedeutet hier: Verschiedene Objekttypen werden über einen gemeinsamen Typ verwendet, reagieren aber mit ihrer jeweils passenden Methodenimplementierung.
Betrachte Meldung m = new EMailMeldung(); und danach m.senden();:
- Die Variable besitzt den allgemeinen Typ
Meldung. - Sie verweist tatsächlich auf ein Objekt der Klasse
EMailMeldung. - Zur Laufzeit wird deshalb
senden()vonEMailMeldungausgeführt.
Diese Auswahl der konkreten Methode zur Laufzeit heißt dynamisches Binden.
Eine Versandliste enthält E-Mail- und SMS-Meldungen. Eine Schleife behandelt jedes Element als Meldung und ruft senden() auf.
Für ein E-Mail-Objekt läuft die E-Mail-Implementierung, für ein SMS-Objekt die SMS-Implementierung. Wird später eine austauschbare PushMeldung ergänzt, muss die Schleife nicht nach konkreten Typen unterscheiden.
Die Austauschbarkeit besitzt eine wichtige Bedingung: Eine Subklasse muss den Vertrag des allgemeinen Typs einhalten. Ein Objekt darf nicht nur denselben Methodennamen tragen, sondern muss sich für die zugesagte Aufgabe passend verhalten.
Wann Objektorientierung hilft und wann sie belastet
Objektorientierung kann Zuständigkeiten sichtbar machen, interne Daten schützen und neue austauschbare Typen ermöglichen. Sie ist aber kein automatisches Gütesiegel.
| Hilfreich | Problematisch |
|---|---|
| Objekte besitzen klar getrennte Verantwortlichkeiten. | Viele kleine Klassen erschweren den Überblick. |
| Stabile Schnittstellen verbergen veränderliche Details. | Unnötige Abstraktion macht eine einfache Aufgabe kompliziert. |
| Austauschbare Typen ersparen Fallunterscheidungen. | Tiefe Vererbungshierarchien machen Verhalten schwer nachvollziehbar. |
| Gemeinsame Funktionalität wird sinnvoll wiederverwendet. | Vererbung wird nur zur Vermeidung weniger Codezeilen eingesetzt. |
Frage vor einer Vererbung: Ist jedes Objekt der Subklasse fachlich überall dort einsetzbar, wo ein Objekt der Superklasse erwartet wird? Wenn nicht, ist die Hierarchie wahrscheinlich unpassend.
Karteikasten
Überlege zuerst selbst und drehe die Karte anschließend zum Prüfen um.
Alles auf einen Blick
- Objektorientierung
- Objekt: Identität, Zustand und Verhalten
- Klasse: gemeinsamer Aufbau für Instanzen
- Konstruktor: gültiger Anfangszustand
- Kapselung: kontrollierter Zugriff über Methoden
- Kooperation: Aufträge an zuständige Objekte
- Vererbung: fachliche Spezialisierung
- Polymorphie: gemeinsame Verwendung austauschbarer Typen
- Dynamisches Binden: Methodenwahl nach tatsächlichem Objekt
Mit Google fortfahren