Praktische Informatik
Objektorientierung: Kapselung, Vererbung, Polymorphie
Wie man neuen Code hinzufügt, ohne bestehenden zu ändern, und woran man erkennt, dass Vererbung die falsche Wahl ist.
Benötigte Grundlagen
Dieses Vorwissen brauchst du für das Kapitel. Schau kurz nach, wenn dir etwas davon nicht mehr präsent ist, sonst leg direkt los.
Einführung
Ein Zeichenprogramm kennt Kreise, Rechtecke und Dreiecke. Nun soll ein Fünfeck dazukommen.
In einem Programm ohne Objektorientierung muss man dafür jede Stelle suchen, an der Formen behandelt werden, und überall einen Fall ergänzen. Vergisst man eine, entsteht ein Fehler, der oft erst viel später auffällt.
Mit Objektorientierung schreibt man eine neue Klasse und ändert am bestehenden keine Zeile. Wie das möglich ist, ist der Inhalt dieses Kapitels. Der Schlüssel heißt Polymorphie, und er ist der eigentliche Gewinn des ganzen Ansatzes.
Das kannst du nach diesem Kapitel
Kapselung erläutern und die Sichtbarkeitsstufen begründen.
Vererbung anwenden und Ober- von Unterklasse unterscheiden.
Überschreiben von Methoden erklären.
Polymorphie an einem Beispiel erläutern und ihren Nutzen benennen.
begründet zwischen Vererbung und Komposition wählen.
Kurz aufgefrischt
Vorausgesetzt werden Klasse, , , Methode und das Klassendiagramm aus Objektorientierte Modellierung.
Neu sind hier die drei Mittel, die aus der Modellierung eine tragfähige Bauweise für große Programme machen: Kapselung, Vererbung und Polymorphie.
Kapselung
Eine Klasse legt fest, welche ihrer Bestandteile von außen erreichbar sind:
| Kennzeichen | Sichtbarkeit | Bedeutung |
|---|---|---|
| privat | nur innerhalb der Klasse | |
| öffentlich | für alle sichtbar | |
| geschützt | auch für Unterklassen |
Üblich ist: privat, Methoden öffentlich. Der Zugriff läuft über Methoden.
Warum dieser Umweg? Sieh dir den Unterschied an:
konto.kontostand = -5000 // direkt: nichts prüft etwas
konto.abheben(5000) // über Methode: die Klasse prüft
Im ersten Fall kann jede Stelle des Programms jeden Wert setzen, auch einen unsinnigen. Im zweiten entscheidet die Klasse selbst, ob der Vorgang zulässig ist.
🔴 Der eigentliche Gewinn liegt tiefer, als „vor Fehlern schützen". Die Klasse gibt eine Zusage nach außen: Der Kontostand unterschreitet nie den Verfügungsrahmen. Solange alle Zugriffe über Methoden laufen, gilt diese Zusage immer, ohne dass irgendjemand außerhalb darauf achten muss. Man muss den Rest des Programms nicht kennen, um sicher zu sein.
Das ist derselbe Gedanke wie beim abstrakten : außen die zugesagten Operationen, innen die frei wählbare Umsetzung. Wechselt man später die interne Speicherung, bleibt der benutzende unverändert.
Kapselung: außen die Zusage, innen die Umsetzung
Lies die Vorzeichen, sie sind die ganze Aussage: Beide tragen ein Minus, sind also von außen nicht erreichbar; alle drei Methoden tragen ein Plus. Damit gibt es keinen Weg an der Klasse vorbei, wer den Kontostand ändern will, muss aufrufen, und dort prüft die Klasse selbst, ob der Verfügungsrahmen das hergibt. 🔴 Der Gewinn ist größer als „vor Fehlern schützen“. Die Klasse gibt eine Zusage nach außen: Der Kontostand unterschreitet nie den Rahmen. Solange jeder Zugriff über die Methoden läuft, gilt sie immer. Man muss den Rest des Programms nicht kennen, um sicher zu sein. Wäre öffentlich, müsste man dafür jede Stelle prüfen, die ihn berührt, und beim nächsten neuen Programmteil wieder von vorn. Es ist derselbe Gedanke wie beim abstrakten : außen die zugesagten Operationen, innen die frei wählbare Umsetzung.
Vererbung
Oft sind mehrere Klassen einander ähnlich. Ein Schüler und ein Lehrer haben beide Name und Geburtsdatum, unterscheiden sich aber in anderem.
Die Vererbung zieht das Gemeinsame in eine Oberklasse; die Unterklassen übernehmen es und ergänzen ihr Eigenes.
┌─────────────────┐
│ Person │
│ - name │
│ - geburtsdatum │
│ + alter() │
└────────△────────┘
│
┌─────────┴─────────┐
┌──────┴──────┐ ┌───────┴──────┐
│ Schüler │ │ Lehrer │
│ - klasse │ │ - faecher │
│ + zeugnis() │ │ + stundenplan()│
└─────────────┘ └──────────────┘
Das Dreieck zeigt zur Oberklasse. Ein der Klasse Schüler hat damit , , , und .
Die Prüffrage für jede Vererbung lautet: „Ist ein Schüler eine Person?" Ja. Dann ist Vererbung richtig.
Überschreiben
Eine Unterklasse darf eine geerbte Methode ersetzen. Das nennt man Überschreiben.
Klasse Form:
flaeche(): gib 0 zurück
Klasse Kreis erbt von Form:
flaeche(): gib 3.14159 * radius * radius // überschreibt
Klasse Rechteck erbt von Form:
flaeche(): gib laenge * breite // überschreibt
Wichtig ist der Unterschied zum Überladen: Überschreiben ersetzt eine geerbte Methode gleichen Namens und gleicher Parameter in der Unterklasse. Überladen bedeutet, mehrere Methoden gleichen Namens mit verschiedenen Parametern in derselben Klasse anzubieten. Das eine hat mit Vererbung zu tun, das andere nicht.
Polymorphie
Jetzt der entscheidende Punkt des Kapitels.
Polymorphie heißt: Ein einer Unterklasse darf überall dort stehen, wo die Oberklasse erwartet wird, und beim Aufruf einer überschriebenen Methode wird die Fassung der tatsächlichen Klasse ausgeführt.
formen = [ Kreis(5), Rechteck(3,4), Kreis(2) ]
für jede f in formen:
ausgeben(f.flaeche())
Die weiß nicht, welche Form sie gerade vor sich hat. Sie ruft auf, und jedes Objekt antwortet mit seiner eigenen Berechnung.
🔴 Der Gewinn zeigt sich beim Erweitern. Kommt ein Dreieck dazu, schreibt man:
Klasse Dreieck erbt von Form:
flaeche(): gib 0.5 * grundseite * hoehe
Und ändert an der Schleife keine Zeile. Der bestehende funktioniert weiter, weil er nie nach der konkreten Klasse gefragt hat.
Vergleiche das mit der Alternative ohne Polymorphie:
für jede f in formen:
wenn f ist Kreis: ausgeben(kreisflaeche(f))
sonst wenn f ist Rechteck: ausgeben(rechteckflaeche(f))
sonst wenn f ist Dreieck: ausgeben(dreiecksflaeche(f))
...
Diese Fallunterscheidung steht erfahrungsgemäß nicht an einer Stelle, sondern an einem Dutzend: beim Zeichnen, beim Speichern, beim Berechnen des Umfangs. Jede neue Form verlangt eine Ergänzung an jeder dieser Stellen, und jede vergessene ist ein Fehler.
Man kann den Nutzen in einem Satz fassen: Polymorphie ersetzt Fallunterscheidungen über den Typ durch die Wahl der Klasse. Statt zu fragen „was bist du?", sagt man „tu, was du kannst".
Dreimal derselbe Methodenname
Achte darauf, was in allen vier Kästen gleich ist und was verschieden. Gleich ist der Methodenname . Er steht viermal da. Verschieden sind die , denn ein Kreis rechnet aus seinem Radius, ein Rechteck aus Länge und Breite. Genau daraus entsteht die Polymorphie: Weil jede Unterklasse dieselbe Methode überschreibt, darf eine über eine Liste von Formen laufen und blind aufrufen; ausgeführt wird jedes Mal die Fassung der tatsächlichen Klasse. 🔴 Der Nutzen zeigt sich beim Erweitern, und der ist im Bild ablesbar: Ein viertes Kästchen unter zu hängen ändert an der Schleife keine Zeile. Die Alternative ohne Polymorphie wäre eine Fallunterscheidung „wenn Kreis … sonst wenn Rechteck …“, und die steht erfahrungsgemäß nicht an einer Stelle, sondern an einem Dutzend, beim Zeichnen, beim Speichern, beim Umfang. Jede neue Form verlangt dann eine Ergänzung an jeder dieser Stellen, und jede vergessene ist ein Fehler.
Derselbe Aufruf, drei Antworten
Oben der Bauplan, unten drei und jedes antwortet auf denselben Aufruf mit seiner eigenen Rechnung. Rechne die drei Werte nach: , und . Die , die diese Liste durchgeht, weiß von alledem nichts; sie fragt nicht „was bist du?“, sondern sagt „tu, was du kannst“. 🔴 Beachte auch den Unterschied zwischen den beiden Kreisen: Sie sind dieselbe Klasse und liefern trotzdem verschiedene Ergebnisse, weil sie verschiedene Werte tragen. Polymorphie heißt also nicht „jedes Objekt ist anders“, sondern: Welche Fassung einer Methode läuft, entscheidet die Klasse des Objekts, nicht die Stelle, an der der Aufruf steht.
Abstrakte Klassen
Bei fällt etwas auf: Was soll dort eigentlich zurückgeben? Die 0 oben ist ein Notbehelf, denn eine „Form" ohne nähere Angabe hat keine Fläche.
Eine abstrakte Klasse löst das. Sie schreibt vor, dass jede Unterklasse eine bestimmte Methode besitzen muss, gibt aber selbst keine Umsetzung an, und von ihr lassen sich keine erzeugen.
abstrakte Klasse Form:
abstrakt flaeche() // keine Umsetzung, Pflicht für Unterklassen
beschreibung(): // normale geerbte Methode
gib "Fläche: " + flaeche()
Das ist mehr als Kosmetik: Eine Unterklasse, die vergisst, wird schon vom Übersetzer beanstandet, statt später eine sinnlose 0 zu liefern.
Vererbung oder Komposition?
Der häufigste Entwurfsfehler ist Vererbung an der falschen Stelle. Die Unterscheidung ist einfach:
| Beziehung | Mittel | Beispiel |
|---|---|---|
| ist ein | Vererbung | Ein Kreis ist eine Form. |
| hat ein | Komposition | Ein Auto hat einen Motor. |
Ein Auto ist kein Motor, also darf nicht von erben. Stattdessen bekommt ein vom Typ ; das nennt man Komposition.
Ein verbreiteter Fehlgriff ist auch erbt von . Geometrisch stimmt es, und trotzdem geht es schief: Ein Rechteck erlaubt, Länge und Breite unabhängig zu setzen, ein Quadrat nicht. Die Unterklasse kann die Zusagen der Oberklasse also nicht einhalten. Die Prüfung „ist ein" allein reicht deshalb nicht; man muss zusätzlich fragen, ob die Unterklasse überall dort einsetzbar ist, wo die Oberklasse steht.
Der häufigste Entwurfsfehler
Die Prüffrage in der ersten Zeile entscheidet fast jeden Fall: Ein Auto ist kein Motor, also darf nicht von erben; es hat einen, bekommt also ein dieses Typs. 🔴 Die Fußzeile ist der Grund, warum die Frage allein trotzdem nicht reicht, und sie ist der berühmteste Fallstrick des Themas. Ein Quadrat ist geometrisch zweifellos ein Rechteck, und trotzdem geht die Vererbung schief: Ein Rechteck sagt zu, dass man Länge und Breite unabhängig setzen kann, ein Quadrat kann diese Zusage nicht einhalten. Wer also ein Quadrat dort einsetzt, wo ein Rechteck erwartet wird, bekommt Überraschungen. Die vollständige Prüfung lautet deshalb: Ist die Unterklasse überall dort einsetzbar, wo die Oberklasse steht, ohne deren Zusagen zu brechen?
Die drei Mittel im Zusammenspiel
| Mittel | Leistet |
|---|---|
| Kapselung | schützt Zusagen über den inneren |
| Vererbung | vermeidet doppelten für Gemeinsames |
| Polymorphie | erlaubt Erweiterung ohne Änderung des Bestehenden |
Die Reihenfolge ist keine Rangfolge, aber die dritte Zeile ist der Grund, aus dem sich der Ansatz durchgesetzt hat.
Polymorphie beim Erweitern
Ein Programm berechnet die Gesamtfläche einer Liste von Formen. Zeige, was beim Hinzufügen eines Dreiecks zu ändern ist, mit und ohne Polymorphie.
- 1
Mit Polymorphie, der bestehende :
abstrakte Klasse Form: abstrakt flaeche() Klasse Kreis erbt von Form: flaeche(): gib 3.14159 * r * r Klasse Rechteck erbt von Form: flaeche(): gib a * b gesamtflaeche(formen): summe = 0 für jede f in formen: summe = summe + f.flaeche() gib summe - 2
Die Erweiterung: eine neue Klasse, sonst nichts.
Klasse Dreieck erbt von Form: flaeche(): gib 0.5 * g * h - 3
An ändert sich keine Zeile. Die hat nie gefragt, welche Form vorliegt; sie ruft auf, und das antwortet selbst.
- 4
Ohne Polymorphie sähe die Funktion so aus:
gesamtflaeche(formen): summe = 0 für jede f in formen: wenn f ist Kreis: summe = summe + kreisflaeche(f) sonst wenn f ist Rechteck: summe = summe + rechteckflaeche(f) gib summe - 5
Hier muss ein weiterer Zweig eingefügt werden. Und dieselbe Fallunterscheidung steht typischerweise auch in , und ; jede vergessene Stelle ist ein Fehler, der erst später auffällt.
- 6
Die Bilanz: Polymorphie verschiebt das Wissen darüber, wie eine Fläche berechnet wird, von den vielen benutzenden Stellen in die eine Klasse, die es betrifft. Genau das macht Programme erweiterbar.
Mit Polymorphie genügt eine neue Klasse; ohne sie muss jede Fallunterscheidung im ganzen Programm ergänzt werden.
Vererbung oder Komposition entscheiden
Prüfe für diese Paare, ob Vererbung oder Komposition richtig ist: (1) Auto und Motor, (2) Sparkonto und Konto, (3) Buch und Seite, (4) Quadrat und Rechteck.
- 1
Das Verfahren: Man setzt beide Sätze und schaut, welcher stimmt. „X ist ein Y" → Vererbung. „X hat ein Y" → Komposition.
- 2
(1) Auto und Motor: „Ein Auto ist ein Motor" ist falsch. „Ein Auto hat einen Motor" stimmt. → Komposition: bekommt ein vom Typ .
- 3
(2) Sparkonto und Konto: „Ein Sparkonto ist ein Konto" stimmt. → Vererbung: erbt von und ergänzt etwa den Zinssatz.
- 4
(3) Buch und Seite: „Ein Buch ist eine Seite" ist falsch. „Ein Buch hat Seiten" stimmt, und zwar viele. → Komposition, hier mit einer Liste von Seiten.
- 5
(4) Quadrat und Rechteck: „Ein Quadrat ist ein Rechteck" stimmt geometrisch. Trotzdem ist Vererbung hier problematisch.
- 6
Warum: Ein Rechteck sagt zu, dass sich Länge und Breite unabhängig setzen lassen. Ein Quadrat kann diese Zusage nicht halten; setzt man dort die Länge, muss sich die Breite mitändern. , der mit einem Rechteck rechnet, verhält sich mit einem Quadrat also plötzlich anders.
- 7
Folgerung: Die Frage „ist ein" ist notwendig, aber nicht hinreichend. Zusätzlich muss gelten, dass die Unterklasse überall einsetzbar ist, wo die Oberklasse steht. Sauberer wäre hier eine gemeinsame Oberklasse ohne die Zusage unabhängiger Seiten.
(1) und (3) Komposition, (2) Vererbung, (4) trotz „ist ein" keine Vererbung, weil das Quadrat die Zusagen des Rechtecks nicht halten kann.
Typischer Fehler
„Vererbung ist praktisch, um wiederzuverwenden. Wenn zwei Klassen ähnliche Methoden brauchen, lasse ich die eine von der anderen erben."
Wiederverwendung ist eine Folge richtiger Vererbung, nicht ihr Grund. Wer sie zum Grund macht, baut Beziehungen, die inhaltlich falsch sind.
Ein bekanntes Beispiel: erbt von , weil eine Liste schon Einfügen und Löschen kann. Was passiert dann? Der erbt auch und . Damit lässt sich mitten im Stapel etwas herausnehmen, und genau das sollte die Struktur ja verhindern. Die LIFO-Zusage ist zerstört.
Richtig ist Komposition: hat eine Liste als privates und bietet nach außen nur und an. Der Code wird genauso wiederverwendet, aber die Zusage bleibt.
Die Prüfung besteht aus zwei Fragen, und beide müssen mit Ja beantwortet sein:
Ist X wirklich ein Y? Kann X überall dort stehen, wo Y erwartet wird, ohne dass sich etwas anders verhält?
Ein weiteres Warnzeichen ist tiefe Verschachtelung. Erbt von , das von , das von erbt, wird schwer nachvollziehbar, woher eine Methode stammt und welche Fassung gilt. Als Faustregel gilt: Vererbung sparsam und flach einsetzen, Komposition großzügig.
Übung 1
leichta) Was bedeuten die Kennzeichen , und im ? b) Warum sollten privat sein? c) Entscheide jeweils zwischen Vererbung und Komposition: Fahrrad und Rad, Hund und Tier, Computer und Festplatte, Roman und Buch.
Tipp anzeigen
Zu c): Sprich beide Sätze aus, „ist ein" und „hat ein".
Lösung anzeigen
a) privat (nur innerhalb der Klasse), öffentlich (für alle sichtbar), geschützt (auch für Unterklassen).
b) Damit der Zugriff über Methoden läuft und die Klasse prüfen kann, ob ein Wert zulässig ist. Dadurch kann sie eine Zusage über ihren einhalten, etwa dass ein Kontostand nie unter den Verfügungsrahmen fällt. Ohne Kapselung müsste jede Stelle des Programms selbst darauf achten.
c) Fahrrad und Rad: „Ein Fahrrad hat Räder" → Komposition. Hund und Tier: „Ein Hund ist ein Tier" → Vererbung. Computer und Festplatte: „Ein Computer hat eine Festplatte" → Komposition. Roman und Buch: „Ein Roman ist ein Buch" → Vererbung.
Detaillierte Schritterklärung anzeigen
Hier wird jeder Schritt einzeln erklärt, vor allem, warum er gemacht wird.
✦ Empfohlen: Standard – Die normale Erklärungstiefe passt zum Einstieg.
- 1
a) Die drei Sichtbarkeitszeichen
bedeutet privat (nur innerhalb der Klasse), bedeutet öffentlich (für alle sichtbar), bedeutet geschützt (auch für Unterklassen).
- 2
b) Warum Attribute privat sein sollten
Damit der Zugriff über Methoden läuft und die Klasse prüfen kann, ob ein Wert zulässig ist. Dadurch kann sie eine Zusage über ihren Zustand einhalten, etwa dass ein Kontostand nie unter den Verfügungsrahmen fällt. Ohne Kapselung müsste jede Stelle des Programms selbst darauf achten.
- 3
c) Vererbung oder Komposition: beide Sätze aussprechen
Der Prüfstein sind zwei Sätze: „ist ein“ deutet auf Vererbung, „hat ein“ auf Komposition. Fahrrad und Rad: „Ein Fahrrad hat Räder“ → Komposition. Hund und Tier: „Ein Hund ist ein Tier“ → Vererbung. Computer und Festplatte: „hat eine“ → Komposition. Roman und Buch: „ist ein“ → Vererbung.
Übung 2
mittelEine Schulverwaltung soll Personen erfassen: Schüler (mit Klasse und Noten) und Lehrer (mit Fächern und Stundenzahl). Beide haben Name und Geburtsdatum.
a) Entwirf die Klassenstruktur mit Vererbung und gib die Sichtbarkeiten an. b) Beide sollen eine Methode haben, die unterschiedlich ausfällt. Wie setzt man das um? c) Erkläre, was beim Aufruf von auf einer Liste gemischter Personen passiert. d) Nun kommen Hausmeister dazu. Was ist zu ändern?
Tipp anzeigen
Zu c): Woher weiß der Rechner, welche Fassung er ausführen soll?
Lösung anzeigen
a) Struktur:
Klasse Person:
- name
- geburtsdatum
+ alter()
+ beschreibung()
Klasse Schüler erbt von Person:
- klasse
- noten
+ durchschnitt()
+ beschreibung() // überschrieben
Klasse Lehrer erbt von Person:
- faecher
- stundenzahl
+ beschreibung() // überschrieben
privat (), Methoden öffentlich (). Sollen die Unterklassen direkt auf zugreifen, wäre (geschützt) angebracht.
b) Durch Überschreiben: Die Oberklasse legt die Methode fest, jede Unterklasse gibt ihre eigene Umsetzung an.
liefert etwa „Anna Meier, Klasse 12". liefert etwa „Herr Weber, Mathematik und Physik".
Sinnvoll ist, abstrakt zu machen, denn eine Person ohne nähere Bestimmung soll es nicht geben.
c) Es greift die Polymorphie. Der kann über die Liste laufen und für jedes Element aufrufen, ohne zu wissen, ob es ein Schüler oder ein Lehrer ist. Ausgeführt wird jeweils die Fassung der tatsächlichen Klasse des , nicht die der Oberklasse.
d) Es kommt eine neue Klasse hinzu:
Klasse Hausmeister erbt von Person:
- zustaendigkeit
+ beschreibung()
Am bestehenden Code ändert sich nichts. Die über die Personenliste funktioniert unverändert, weil sie nie nach der konkreten Klasse gefragt hat. Genau das ist der Gewinn der Polymorphie: Erweiterung ohne Änderung des Bestehenden.
Detaillierte Schritterklärung anzeigen
Hier wird jeder Schritt einzeln erklärt, vor allem, warum er gemacht wird.
✦ Empfohlen: Standard – Die normale Erklärungstiefe passt zum Einstieg.
- 1
Teil a): Das Gemeinsame nach oben ziehen
Man sucht, was beide haben, und stellt es in die Oberklasse. Alles Übrige bleibt bei den Unterklassen. Vorher prüft man die Frage „ist ein": Ein Schüler ist eine Person, ein Lehrer ebenfalls.
Zwischenergebnis
und nach oben, Rest unten.
Wird ein Attribut nur von einer Unterklasse gebraucht, gehört es nicht in die Oberklasse. Sonst tragen alle anderen etwas mit, das sie nichts angeht.
- 2
Teil b): Gleicher Name, eigene Umsetzung
Die Methode wird in der Oberklasse festgelegt und in jeder Unterklasse überschrieben. Der Name und die Parameter müssen dabei übereinstimmen.
Zwischenergebnis
Zwei Fassungen von .
- 3
Teil c): Wer entscheidet, welche Fassung läuft?
Nicht der Typ der entscheidet, sondern die tatsächliche Klasse des Objekts. Deshalb kann derselbe Aufruf verschiedene Ergebnisse liefern.
Zwischenergebnis
Ausgeführt wird die Fassung der tatsächlichen Klasse.
- 4
Teil d): Prüfen, was sich wirklich ändert
Man geht den bestehenden Code durch und sucht Stellen, die die Klassen einzeln aufzählen. Findet man keine, ist auch nichts zu ändern.
Zwischenergebnis
Nur eine neue Klasse, sonst nichts.
Genau daran erkennt man einen guten objektorientierten Entwurf: Eine Erweiterung berührt nur neue Dateien. Muss man an vielen Stellen etwas ergänzen, fehlt irgendwo die Polymorphie.
Übung 3
schwera) Erkläre, warum Polymorphie Programme erweiterbar macht. Vergleiche mit einer Lösung über Fallunterscheidungen. b) Was ist eine abstrakte Klasse, und wann setzt man sie ein? c) Ein Entwickler lässt von erben. Beurteile das. d) Ein Entwickler lässt von erben, um zu sparen. Beurteile das und nenne die bessere Lösung.
Tipp anzeigen
Zu c) und d): Kann die Unterklasse die Zusagen der Oberklasse halten?
Lösung anzeigen
a) Bei Polymorphie steht das Wissen darüber, wie eine Aufgabe zu erledigen ist, in der jeweiligen Klasse. Der benutzende Code ruft nur die Methode auf und fragt nie nach dem Typ. Eine neue Unterklasse bringt ihre Umsetzung selbst mit, und der bestehende Code bleibt unverändert.
Bei Fallunterscheidungen steht die Liste aller bekannten Klassen im benutzenden Code, und zwar an jeder Stelle, an der unterschieden wird: beim Zeichnen, beim Speichern, beim Berechnen. Jede neue Klasse verlangt an all diesen Stellen eine Ergänzung. Vergisst man eine, entsteht ein Fehler, der oft erst spät auffällt, weil der Code weiterläuft und nur ein falsches Ergebnis liefert.
Kurz: Polymorphie verschiebt das Wissen von den vielen benutzenden Stellen in die eine Klasse, die es betrifft.
b) Eine abstrakte Klasse schreibt Methoden vor, ohne sie umzusetzen, und von ihr lassen sich keine erzeugen.
Eingesetzt wird sie, wenn eine Oberklasse nur als gemeinsames Dach dient und für sich genommen keinen Sinn ergibt. Eine „Form" ohne nähere Angabe hat keine Fläche; die Rückgabe 0 wäre ein Notbehelf und würde einen Fehler verschleiern.
Vorteil: Eine Unterklasse, die die vorgeschriebene Methode nicht umsetzt, wird bereits vom Übersetzer beanstandet. Der Fehler tritt damit beim Übersetzen auf und nicht erst im Betrieb.
c) Geometrisch ist „ein Quadrat ist ein Rechteck" richtig, für den Entwurf ist die Vererbung trotzdem problematisch.
Ein Rechteck sagt zu, dass sich Länge und Breite unabhängig voneinander setzen lassen. Ein Quadrat kann das nicht halten: Setzt man die Länge, muss sich die Breite mitändern. Code, der ein Rechteck erwartet und Länge und Breite getrennt setzt, verhält sich mit einem Quadrat also plötzlich anders.
Daraus folgt die verschärfte Prüfregel: „ist ein" ist notwendig, aber nicht hinreichend. Zusätzlich muss die Unterklasse überall einsetzbar sein, wo die Oberklasse steht. Besser wäre hier eine gemeinsame Oberklasse , die keine unabhängigen Seiten zusagt.
d) Das ist ein Entwurfsfehler, und zwar aus demselben Grund wie in c).
würde neben und auch und erben. Damit ließe sich mitten im etwas herausnehmen, und genau das soll die Struktur verhindern. Die LIFO-Zusage wäre nicht mehr garantiert, sondern hinge davon ab, dass niemand die geerbten Methoden benutzt.
Bessere Lösung: Komposition. bekommt eine Liste als privates und bietet nach außen nur , und an. Der Code der Liste wird genauso wiederverwendet, aber die Zusage bleibt erhalten, weil die Liste von außen unerreichbar ist.
Die allgemeine Lehre: Wiederverwendung ist kein Grund für Vererbung. Sie ist eine Folge richtiger Vererbung; als Motiv führt sie zu Beziehungen, die inhaltlich falsch sind.
Detaillierte Schritterklärung anzeigen
Hier wird jeder Schritt einzeln erklärt, vor allem, warum er gemacht wird.
✦ Empfohlen: Standard – Die normale Erklärungstiefe passt zum Einstieg.
- 1
a) Polymorphie verschiebt das Wissen dorthin, wo es hingehört
Bei Polymorphie steht das Wissen darüber, wie eine Aufgabe zu erledigen ist, in der jeweiligen Klasse. Der benutzende Code ruft nur die Methode auf und fragt nie nach dem Typ. Eine neue Unterklasse bringt ihre Umsetzung selbst mit, und der bestehende Code bleibt unverändert.
- 2
b) Abstrakte Klassen: vorschreiben ohne umzusetzen
Eine abstrakte Klasse schreibt Methoden vor, ohne sie umzusetzen, und von ihr lassen sich keine Objekte erzeugen. Eingesetzt wird sie, wenn eine Oberklasse nur als gemeinsames Dach dient und für sich genommen keinen Sinn ergibt, eine „Form“ ohne nähere Angabe hat keine Fläche.
- 3
c) Quadrat erbt von Rechteck: geometrisch richtig, im Entwurf falsch
Geometrisch ist „ein Quadrat ist ein Rechteck“ richtig, für den Entwurf ist die Vererbung trotzdem problematisch. Ein Rechteck sagt zu, dass sich Länge und Breite unabhängig voneinander setzen lassen. Ein Quadrat kann das nicht halten: Setzt man die Länge, muss sich die Breite mitändern.
- 4
d) Stapel erbt von Liste: Wiederverwendung ist kein Grund für Vererbung
Das ist ein Entwurfsfehler, und zwar aus demselben Grund wie in c): würde neben und auch und erben. Damit ließe sich mitten im Stapel etwas herausnehmen, genau das soll die Struktur verhindern. Bessere Lösung: Komposition.
Zusammenfassung
Kapselung macht privat und leitet jeden Zugriff über Methoden; ihr Zweck ist nicht Geheimhaltung, sondern eine Zusage über den inneren , die immer gilt, ohne dass der übrige darauf achten muss. Vererbung zieht Gemeinsames in eine Oberklasse, die Unterklassen ergänzen ihr Eigenes und dürfen geerbte Methoden überschreiben; die Prüffrage lautet „ist ein", während „hat ein" auf Komposition führt. Der eigentliche Gewinn ist die Polymorphie: Ein einer Unterklasse steht überall dort, wo die Oberklasse erwartet wird, und beim Aufruf wird die Fassung der tatsächlichen Klasse ausgeführt. Damit ersetzt sie Fallunterscheidungen über den Typ, und eine neue Unterklasse lässt sich hinzufügen, ohne bestehenden Code zu ändern. Abstrakte Klassen schreiben Methoden vor, ohne sie umzusetzen, und lassen den Übersetzer prüfen, dass jede Unterklasse sie liefert. Die Prüffrage „ist ein" allein genügt allerdings nicht: Die Unterklasse muss die Zusagen der Oberklasse auch halten können, sonst führt die Vererbung wie bei Quadrat und Rechteck oder bei und Liste zu einem falschen Entwurf.


