Objektorientierung - das Konzept Polymorphie
hhttps://bildung.social/@oerinformatik/113407704530797657
https://oer-informatik.de/uml-klassendiagramm-polymorphie
tl/dr; (ca. 6 min Lesezeit): Ein zentrales Konzept der objektorientierten Programmierung (OOP) ist die Polymorphie: die Vielgestalt von Objekten. Es ermöglicht über Instanztypen das Verhalten eines Objekts festzulegen, während der Typ der Referenz festlegt, welche Member erreichbar sind. Objekte sind so flexibel über unterschiedliche Referenzen wiederverwertbar, ohne gecastet werden zu müssen. (Zuletzt geändert am 09.11.2024)
Dieser Text ist ein Teil der Infotext-Serie zu UML-Klassendiagrammen:
Das Konzept der Polymorphie (Vielgestalt) in der OOP
Bei der Vererbung handelt es sich um eine “ist-ein”-Beziehung zwischen erbenden Klassen. Ein GeschaeftsKonto
ist in folgendem Beispiel ein Konto
. Ein SparKonto
ist ein Konto
.

kontenListe
werden alle Instanzen von Konto + Subklassen verwaltetDie Vielgestalt (Polymorphie) der OOP greift diese Eigenschaft auf: Wenn ein Geschäftskonto
ein Konto
ist, dann kann ich es auch immer dann verwenden, wenn ich ein Konto
verwenden kann. Ein Geschäftskonto
kann also in unterschiedlicher Gestalt auftreten und genutzt werden: als Konto
oder als Geschäftskonto
.
Wir können also Objekte verwenden, ohne genau die Klasse zu kennen, aus der sie instanziiert wurden, solange wir eine ihrer Superklassen (oder Interfaces) kennen und nur deren Methoden verwenden.
Das Konzept lässt sich besser verstehen, wenn wir stark typisierte OOP-Sprachen (wie z.B. Java) betrachten, da hier zwischen dem deklarierten Referenztyp der Variablen und der Klasse der zugrundeliegenden Instanz unterschieden wird. Dynamisch typisierte Sprachen (wie z.B. Python) kennen das Konzept eines Referenztyps nicht.
In stark typisierten Sprachen können wir Instanzen einer Subklasse mit Referenzen einer Superklasse ansprechen. Am Beispiel von Java sieht für unser obiges Beispiel etwa so aus:
Konto konto1 = new Konto("DE123");
Konto konto2 = new PrivatKonto("DE456", "Max", "Mustermann");
Konto konto3 = new GeschaeftsKonto("DE789", "Muster GmbH", 400D);
Konto konto4 = new SparKonto("DE987", "Max", "Mustermann", 0.02D);
PrivatKonto konto5 = new SparKonto("DE654", "Max", "Mustermann", 0.01D);
Auch wenn die Referenz vom Typ der Superklasse ist: die aufgerufene Implementierung ist die der jeweiligen Unterklasse. Ein SparKonto
kann nach außen auch die Gestalt eines Konto
oder eines PrivatKonto
annehmen - das ist die Vielgestalt, die Polymorphie.
In stark typisierten OOP-Sprachen wie Java hat das zur Folge, dass wir unterscheiden müssen, wessen Instanz ein Objekt ist und über welche Referenz es angesprochen wird. Wir erweitern unser Ausgangsbeispiel um ein Interface und fügen ein paar Details der Klassen an, um es plastischer zu machen:

kontenListe
werden alle Instanzen von Konto + Subklassen verwaltetMit diesem Wissen betrachten wir die folgenden Zeile genauer:
Konto konto3 = new GeschaeftsKonto("DE789", "Muster GmbH", 400D);
konto3.auszahlen(500D); //nutzt die Implementierung von Geschäftskonto
konto3.setVerfuegungsrahmen(1000); //nicht möglich, da keine Methode von Konto
GeschaeftsKonto konto5 = new GeschaeftsKonto("DE789", "Muster GmbH", 400D);
konto5.auszahlen(500D); //nutzt die Implementierung von Geschäftskonto
konto5.setVerfuegungsrahmen(1000); //möglich, da Geschäftskonto-Referenztyp
Der Referenztyp von konto3
ist Konto
, es ist aber eine Instanz der Klasse GeschaeftsKonto
. Wir glauben also, es mit einem Konto
zu tun zu haben und können nur Konto
-Methoden aufrufen. Der Aufruf konto3.setVerfuegungsrahmen()
spricht keine Methode von Konto
an und ist daher für konto3
nicht verfügbar.
Mit dem Namen konto5
haben wir ein weiteres Geschäftskonto erzeugt, das wir diesmal über eine Geschäftskonto
-Referenz ansprechen. konto5.setVerfuegungsrahmen()
ist also aufrufbar.
Der Referenztyp legt fest, welche Member der Klasse erreichbar sind.
Der Instanztyp von konto3
ist GeschaeftsKonto
. Wenn wir konto3.auszahlen()
aufrufen reagiert die Implementierung auszahlen()
, die in GeschaeftsKonto
festgelegt wurde. Dass konto3
eine Konto
-Referenz ist, spielt hier keine Rolle:
Der Instanztyp legt fest, welche Implementierung bei Aufruf ausgeführt wird.
Die gleichen Überlegungen können wir für Interfaces treffen:
Zahlbar konto6 = new PrivatKonto("DE456", "Max", "Mustermann");
konto6.einzahlen(100); // nutzt die Implementierung, die Privatkonto von Konto geerbt hat
konto6.getName(); // nicht aufrufbar, da keinen Methode von Zahlbar
Der Referenztyp Zahlbar
legt fest, dass wir nur die Methoden des Interfaces Zahlbar
direkt aufrufen können (einzahlen()
, auszahlen()
). Der Instanztyp ist ein Privatkonto
, es wird also die Implementierung genutzt, die in Privatkonto
gilt. In diesem Fall wäre das die von Konto
geerbte Implementierung, da sie nicht überschrieben wurden.
Das ermöglicht uns beispielsweise, alle Instanzen von Konto
und deren Subklassen zentral zu verwalten, in dem die Superklasse eine Liste aller Konten als Klassenvariable (statisch) hält und verwaltet.
Im Quelltext muss das neue Klassenattribut und die Klassenvariable in Konto
eingefügt werden (sowie der Import für java.util.ArrayList
):
private static ArrayList<Konto> kontenListe = new ArrayList<>();
static String listeStatusAllerKonten(){
String ausgabe ="";
for (Konto einKonto: kontenListe){
ausgabe = ausgabe + "\n" + einKonto.toString();
}
return ausgabe;
}
Bereits so würde die polymorphe Struktur sichtbar, da die aufgerufene toString()
-Methode (diese erben alle Java-Objekte von der Klasse Object
) den Instanztyp ausgibt:
Alle Konten:
- de.csbme.kontoverwaltung.Konto@5c8da962
- de.csbme.kontoverwaltung.PrivatKonto@33e5ccce
- de.csbme.kontoverwaltung.GeschaeftsKonto@5a42bbf4
- de.csbme.kontoverwaltung.SparKonto@270421f5
Deutlich komfortabler wird die Ausgabe, wenn jede Klasse eine eigene Implementierung von toString()
enthält, in der alle spezifischen Attributwerte ausgegeben werden (getIban()
muss noch in Konto
implementiert werden):
Für Konto
:
Für PrivatKonto
:
@Override
public String toString(){
return "Privatkonto " + getIban()
+ " von " + getPrivatkundenName() + "/ Kontostand: "+kontostand;
}
Für GeschäftsKonto
:
@Override
public String toString(){
return "Geschäftskonto " + getIban() + " von " + firmenname
+ "/ Kontostand: "+kontostand + " / Verfügungsrahmen: " + verfuegungsrahmen;
}
Für SparKonto
:
@Override
public String toString(){
return "Sparkonto " + getIban()
+ " von " + getPrivatkundenName()
+ " / Kontostand: "+kontostand
+ " / Zins: " + String.format("%.2f", zinssatz*100) + "%";
}
Nach den Anpassungen ist der polymorphe Aufruf unterschiedlicher Instanztypen über eine Konto
-Referenz in der Liste ebenso gut sichtbar, wie die Tatsache, dass jede Instanz die eigene Implementierung nutzt. Zudem sind die bei Aufruf über die Konto
-Referenz zunächst unsichtbaren Methoden und Attribute für die jeweilige (interne) toString()
-Methode weiterhin aufrufbar und Detailinformationen verfügbar:
Alle Konten:
- Konto DE123 / Kontostand 500.0
- Privatkonto DE456 von Max Mustermann/ Kontostand: 1000.0
- Geschäftskonto DE789 von Muster GmbH/ Kontostand: 1601.0 / Verfügungsrahmen: 400.0
- Sparkonto DE987 von Max Mustermann/ Kontostand: 102.0 / Zins: 2,00%
Hinweis zur Nachnutzung als Open Educational Resource (OER)
Dieser Artikel und seine Texte, Bilder, Grafiken, Code und sonstiger Inhalt sind - sofern nicht anders angegeben - lizenziert unter CC BY 4.0. Nennung gemäß TULLU-Regel bitte wie folgt: “Objektorientierung - das Konzept Polymorphie” von oer-informatik.de (H. Stein), Lizenz: CC BY 4.0. Der Artikel wurde unter https://oer-informatik.de/uml-klassendiagramm-polymorphie veröffentlicht, die Quelltexte sind in weiterverarbeitbarer Form verfügbar im Repository unter https://gitlab.com/oer-informatik/uml/umlklasse. Stand: 09.11.2024.
[Kommentare zum Artikel lesen, schreiben] / [Artikel teilen] / [gitlab-Issue zum Artikel schreiben]