Informatik

Polymorphie

Am Ende der Einführungsphase hast du eine Zusatzaufgabe bekommen: Ein Feld vom Typ Konto[] aufnehmen zu lassen, und dann hebeAb(100) auf allen Konten aufzurufen. Bei den Girokonten klappte es bis zum Dispolimit, bei den Sparkonten nur bis zum Guthaben.

Woher weiß Java zur Laufzeit, welche der beiden Methoden es nehmen muss? Die Antwort heißt Polymorphie – Vielgestaltigkeit.

Generalisierung und Spezialisierung

Eine Vererbungsbeziehung lässt sich aus zwei Richtungen lesen:

  • Von unten nach oben ist es eine Generalisierung: Man erkennt, was mehrere Klassen gemeinsam haben, und zieht es in eine Oberklasse. Aus Girokonto und Sparkonto wird Konto.
  • Von oben nach unten ist es eine Spezialisierung: Man nimmt eine allgemeine Klasse und ergänzt Besonderheiten. Aus Konto wird Girokonto mit einem Dispolimit.

Beides beschreibt dieselbe Beziehung. Welche Richtung du gehst, hängt davon ab, wie du zu deinem Entwurf kommst.

Ein Objekt, zwei Typen

Sage die Ausgabe ohne Rechner voraus. Erkläre danach für jedes der drei Konten, welche Fassung von hebeAb ausgeführt wurde.

Auflösung
  Ada: false, Stand 100.0
  Alan: true, Stand -300.0
  Grace: false, Stand 100.0
  • Ada hat ein Konto. Die Fassung aus Konto erlaubt nur bis zum Guthaben – 400 sind zu viel.
  • Alan hat ein Girokonto. Die überschriebene Fassung erlaubt bis zum Dispolimit – 400 gehen durch.
  • Grace hat ein Sparkonto. Diese Klasse überschreibt hebeAb nicht, also gilt die geerbte Fassung aus Konto.

Polymorphie: Eine Variable vom Typ der Oberklasse kann auf Objekte jeder Unterklasse verweisen. Beim Aufruf einer Methode entscheidet nicht der Typ der Variablen, sondern der tatsächliche Typ des Objekts, welche Fassung ausgeführt wird.

Diese Entscheidung fällt erst zur Laufzeit. Man spricht deshalb auch von dynamischer Bindung oder später Bindung.

Statischer und dynamischer Typ

Die Variable k hat zwei Typen:

  • Den statischen Typ Konto – so ist sie deklariert. Er entscheidet, welche Methoden man aufrufen darf. Deshalb geht k.getDispolimit() nicht: Konto kennt diese Methode nicht.
  • Den dynamischen Typ Girokonto – so ist das Objekt tatsächlich beschaffen. Er entscheidet, welche Fassung ausgeführt wird. Deshalb greift beim Abheben das Dispolimit.

Kurz: Der statische Typ bestimmt das Was darf ich?, der dynamische das Was passiert?

Entferne im Hauptprogramm die Schrägstriche und lies die Fehlermeldung.

Beschreibe danach, wie man an das Dispolimit trotzdem herankäme – und warum man das nur selten tun sollte.

Auflösung

Mit einer Typumwandlung nach unten:

Girokonto g = (Girokonto) k;
IO.println(g.getDispolimit());

Man sollte das selten tun, weil man damit die Polymorphie gerade aushebelt. Solche Umwandlungen sind außerdem gefährlich: Verweist k in Wirklichkeit auf ein Sparkonto, bricht das Programm zur Laufzeit ab.

Faustregel: Wenn du im Programm anfängst, nach dem konkreten Typ zu fragen und umzuwandeln, stimmt meist der Entwurf nicht. Dann fehlt der Oberklasse eine Methode.

Der eigentliche Gewinn

Angenommen, es kommt eine vierte Kontoart dazu: ein Festgeldkonto, von dem gar nicht abgehoben werden darf.

a) Was musst du am Hauptprogramm ändern, das über alle Konten läuft?

b) Was wäre zu ändern gewesen, wenn du das Programm stattdessen mit einer if-Kette über die Kontoarten gebaut hättest?

Auflösung

a) Nichts. Du schreibst die neue Klasse, überschreibst hebeAb und legst ein Objekt davon ins Feld. Das Hauptprogramm bleibt unverändert.

b) Bei einer if-Kette wie

if (art.equals("giro")) { ... }
else if (art.equals("spar")) { ... }

müsstest du jede solche Kette im ganzen Programm um einen Fall erweitern. Und wenn du eine übersiehst, merkst du es erst, wenn etwas schiefgeht.

Genau das ist der Gewinn: Polymorphie verlagert die Fallunterscheidung von vielen if-Ketten in eine Vererbungshierarchie. Neues Verhalten kommt durch eine neue Klasse dazu, nicht durch Änderungen an bestehendem Code.

Aufgabe: Formen

Setze eine Vererbungshierarchie für geometrische Formen um, sodass alle Tests grün werden.

Achte darauf, welche Klassen flaeche() überschreiben müssen und welche nicht.

classDiagram
    class Form {
        #String bezeichnung
        +Form(String pBezeichnung)
        +String getBezeichnung()
        +double flaeche()
        +String beschreibung()
    }
    class Rechteck {
        -double breite
        -double hoehe
        +Rechteck(double pBreite, double pHoehe)
        +double flaeche()
    }
    class Quadrat {
        +Quadrat(double pSeite)
    }
    class Kreis {
        -double radius
        +Kreis(double pRadius)
        +double flaeche()
    }
    Form <|-- Rechteck
    Rechteck <|-- Quadrat
    Form <|-- Kreis
Tipp 1: Warum überschreibt Quadrat die Flächenmethode nicht?

Weil ein Quadrat ein Rechteck mit gleichen Seiten ist. Der Konstruktor gibt die Seitenlänge zweimal an Rechteck weiter – damit stimmt die geerbte Rechnung bereits.

Das ist Spezialisierung in Reinform: Nur das Besondere wird ergänzt, hier die Bezeichnung.

Tipp 2: Die Bezeichnung im Quadrat ändern

bezeichnung ist in Form als protected deklariert. Eine Unterklasse darf also direkt darauf schreiben:

bezeichnung = "Quadrat";

Das muss nach dem super(...)-Aufruf stehen – vorher gibt es das Attribut noch nicht.

Tipp 3: Der letzte Test

beschreibung() steht nur in Form und wird nirgends überschrieben. Trotzdem gibt sie beim Quadrat 25.0 aus.

Warum? Weil sie intern flaeche() aufruft – und dieser Aufruf wird dynamisch gebunden. Auch aus einer geerbten Methode heraus greift die Fassung des tatsächlichen Objekts.

Lösung. Erfrage das Passwort bei deiner Lehrkraft.

Zusatzaufgabe

Die Klasse Form hat eine Schwachstelle: flaeche() liefert dort 0.0. Das ist eine Notlüge – eine „allgemeine Form“ hat gar keinen sinnvollen Flächeninhalt.

a) Schreibe ein Programm, das new Form("irgendwas") erzeugt und die Fläche ausgibt. Ist das Ergebnis sinnvoll?

b) Welches Problem entsteht, wenn jemand vergisst, flaeche() in einer neuen Unterklasse zu überschreiben?

c) Wie müsste man Form gestalten, damit man sie gar nicht erst erzeugen kann und das Überschreiben erzwungen wird?

Die Antwort auf c) ist das Thema der nächsten Lektion.


Selbsttest

Polymorphie

Teilbare URL erstellen

Abschnitte auswählen