Klassenbeziehungen: Vererbung von Klassen in der UML
https://bildung.social/@oerinformatik/
https://oer-informatik.de/uml-klassendiagramm-vererbung
tl/dr; (ca. 15 min Lesezeit): Zentrales Element der Code-Wiederverwendung in der OOP ist die Vererbung. Am Beispiel eines Investments werden hier das OOP-Prinzip Vererbung und die Konzepte Überschreiben und Polymorphie erklärt. (Zuletzt geändert am 24.05.2023)
Dieser Text ist ein Teil der Infotext-Serie zu UML-Klassendiagrammen:
In der Objektorientierten Programmierung (OOP) wird das Konzept Vererbung genutzt, um Struktur und Verhalten von Klassen mehrfach nutzen zu können. Man erhält so die Möglichkeit, spezialisierte Klasse auf Grundlage einer allgemeineren Klasse zu bilden. Die spezialisierten Klassen können Struktur und Verhalten der vererbenden Klasse übernehmen oder anpassen - die Implementierung der vererbenden Klasse ist die Basis, die von den erbenden Klassen übernommen werden kann.

Wir betrachten eine allgemeinere Klasse Konto
und erbende Klassen PrivatKonto
und GeschäftsKonto
. In der UML wird die Vererbung über einen Pfeil mit nicht ausgefüllter dreieckiger Pfeilspitze, die auf die allgemeinere Klasse zeigt, notiert.
Die Klasse Konto
verfügt bereits über die Basisfunktionalitäten, die ein Konto benötigt: die Attribute iban
und kontostand
, die Methoden einzahlen()
und auszahlen()
.
Jedoch unterscheiden sich die spezialisierten Konten von der allgemeinen Form darin, dass sie auf unterschiedliche Weise die Kontoinhaber erfassen: der Kontoinhaber eines Privatkontos hat einen Vor- und Nachnamen, während bei einem Geschäftskonto eine Firmenbezeichnung und eine Geschäftsform zur Identifizierung dient.
In der Literatur finden sich eine Vielzahl von Bezeichnungen für diese Beziehung zwischen zwei Klassen:
Konto | Privatkonto Geschäftskonto |
---|---|
Superklasse | Subklasse |
Elternklasse | Kindklasse |
Oberklasse | Unterklasse |
Basisklasse | abgeleitete Klasse |
generalisierte Klasse | spezialisierte Klasse |
allgemeine Klasse | spezifische Klasse |
Das Ausgangsbeispiel als Quelltext
Die Basisklasse Konto
Konto verfügt über zwei Attribute, drei Methoden und einen Konstruktor:

Als Java-Quelltext könnte die Klasse beispielsweise so aussehen:
public class Konto {
private String iban;
private double kontostand;
public Konto(String iban) {
this.iban = iban;
kontostand = 0;
}
public void einzahlen(double betrag) {
kontostand = kontostand + betrag;
}
public boolean auszahlen(double betrag) {
if ((kontostand - betrag) > 0) {
kontostand = kontostand - betrag;
return true;
} else {
return false;
} }
public double getKontostand() {
return kontostand;
} }
Das Konzept der Erweiterung in der abgeleiteten Klasse PrivatKonto
PrivatKonto
verfügt über alle Methoden und Attribute von Konto
, erweitert diese aber um zusätzliche Attribute und Methoden (vorname
, nachname
, getPrivatekundeName()
). Weiterhin verfügt PrivatKonto
über einen eigenen Konstruktor, der alle erforderlichen Attribute setzt:

Java-Quelltext für diese Klasse sähe beispielsweise so aus:
public class PrivatKonto extends Konto{
private String vorname;
private String nachname;
public PrivatKonto(String iban, String vorname, String nachname){
super(iban);
this.vorname = vorname;
this.nachname = nachname;
}
public String getPrivatkundenName(){
return this.vorname + " " + this.nachname;
} }
In der Programmiersprache Java ist es erforderlich, dass der Konstruktor der Superklasse (Konto(iban)
) in der ersten Zeile der Subklasse aufgerufen wird. Dies geschieht über die Referenz mit dem Keyword super(...);
Die vorhandene Basisklasse Konto
wird durch eine Methode, einen Konstruktor und zwei Attribute erweitert.
Auf die Attribute iban
und kontostand
der Basisklasse kann die Klasse PrivatKonto
nicht zugreifen, da diese die Sichtbarkeit private
haben. Der Zugriff kann nur über die öffentlichen Methoden von Konto
erfolgen (in Python: über die Properties).
Das Konzept Überschreiben - die abgeleitete Klasse GeschäftsKonto

In der Klasse GeschaeftsKonto
wird die Basisklasse Konto
wieder um einige Attribute (firmenname
, verfuegungsrahmen
) und Methoden (Konstruktor, Getter und Setter) erweitert.
Zusätzlich wird jedoch auch eine Methode überschrieben: Die Methode auszahlen()
wird in Geschäftskonto neu implementiert. Es soll hier zusätzlich der Verfügungsrahmen geprüft werden.
Eine beispielhafte Implementierung in Java könnte etwa so aussehen:
public class GeschaeftsKonto extends Konto {
private String firmenname;
private double verfuegungsrahmen;
public double getVerfuegungsrahmen() {return verfuegungsrahmen;}
public void setVerfuegungsrahmen(double verfuegungsrahmen) {this.verfuegungsrahmen = verfuegungsrahmen;}
public GeschaeftsKonto(String iban, String firmenname, double verfuegungsrahmen) {
super(iban);
this.firmenname = firmenname;
this.setVerfuegungsrahmen(verfuegungsrahmen);
}
@Override
public boolean auszahlen(double betrag) {
if (betrag > verfuegungsrahmen) {
return false;
} else {
return super.auszahlen(betrag);
} } }
Die Annotation @Override
kann in Java vor Methoden geschrieben werden, die eine bereits vorhandene Methode einer Superklasse überschreiben. Konto.auszahlen()
bleibt vorhanden, wird aber von der überschreibenden Methode verdeckt.
Da Geschäftskonto aufgrund der Sichtbarkeitsmodifikatoren in Konto
den Kontostand nicht selbst verändern kann nutzt es die Methoden von Konto, um die Auszahlung durchzuführen. Über die super
-Referenz wird wieder auf die Superklasse verwiesen und mit super.auszahlung(betrag)
eine Methode aufgerufen, die den Kontostand ändern darf (da sie Bestandteil von Konto ist und somit auf private
Attribute von Konto zugreifen darf).
Das Konzept der partiellen Sichtbarkeit (protected
)
Eine erbende Klasse von Konto
kann das Attribut kontostand
nicht verändern. Wenn wir für eine neue Klasse SparKonto
eine Methode verzinse()
schreiben wollen, so kann diese derzeit nicht direkt den Kontostand um die aufgelaufenen Zinsen erhöhen.
Es gibt viele vergleichbare Anwendungsfälle, in denen wichtig ist, dass auch die erbenden Klassen Zugriff auf geschützte Attribute und Methoden haben. Daher stellt die UML (und viele OOP-Sprachen, jedoch nicht Python) einen weiteren Sichtbarkeitsmodifikator zur Verfügung, der Zugriff innerhalb der Klasse und für alle erbenden Klassen einräumt: protected
(im UML-Klassendiagramm: #
).

protected
gesetztIm obigen UML-Klassendiagramm ist kontostand
als protected
notiert (mit #
). Somit hat die über PrivatKonto
erbende Klasse Sparkonto
Zugriff auf das Attribut kontostand
.
Im Java Quelltext dargestellt wären folgende Anpassungen nötig: In der Klasse Konto
muss der Sichtbarkeitsmodifikator von private
auf protected
geändert werden.
Somit hätte eine Klasse Sparkonto
die Möglichkeit, die Zinsen in der Methode verzinse()
direkt aufzuschlagen:
public class SparKonto extends PrivatKonto{
private double zinssatz;
public double getZinssatz() {return zinssatz;}
public void setZinssatz(double zinssatz) {this.zinssatz = zinssatz;}
public SparKonto(String iban , String vorname, String nachname, double startzins ){
super(iban, vorname, nachname);
this.setZinssatz(startzins);
}
public void verzinse(){
this.kontostand = this.kontostand * (1+zinssatz);
} }
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 ein Konto
. Ein SparKonto
ist ein Konto
.
Die Vielgestalt (Polymorphie) der OOP greift diese Eigenschaft auf: Wenn ein Geschäftskonto
ein Konto
ist, dann kann ich es auch wie ein Konto
verwenden. 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 Superklasse 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 Referenzdatentyp der Variablen und der Klasse der Instanz unterschieden wird. Dynamisch typisierte Sprachen (wie z.B. Python) kennen das Konzept eines Referenzdatentyps nicht.
In stark typisierten Sprachen können wir Instanzen einer Subklasse mit Referenzen einer Superklasse ansprechen. Am Beispiel von Java sieht das 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.
Der Referenztyp von konto3
ist Konto
. Wir glauben also, es mit einem Konto
zu tun zu haben und können nur dessen Methoden aufrufen. setVerfuegungsrahmen()
ist keine Methode von Konto
und daher für konto3
nicht aufrufbar.
Der Referenzdatentyp 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 Instanzdatentyp legt fest, welche Implementierung bei Aufruf ausgeführt wird.
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.

kontenListe
werden alle Instanzen von Konto + Subklassen verwaltet
Im Quelltext muss das neue Klassenattribut und die Klassenvariable in Konto
eingefügt werden (sowie der Import für java.util.ArrayList
):
static ArrayList<Konto> kontenListe = new ArrayList<>();
public static String listeStatusAllerKonten(){
String ausgabe ="Alle Konten:";
for (Konto einKonto: kontenListe){
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 (wie im UML-Diagramm angegeben) 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: “Klassenbeziehungen: Vererbung von Klassen in der UML” von oer-informatik.de (H. Stein), Lizenz: CC BY 4.0. Der Artikel wurde unter https://oer-informatik.de/uml-klassendiagramm-vererbung veröffentlicht, die Quelltexte sind in weiterverarbeitbarer Form verfügbar im Repository unter https://gitlab.com/oer-informatik/uml/umlklasse. Stand: 24.05.2023.
[Kommentare zum Artikel lesen, schreiben] / [Artikel teilen] / [gitlab-Issue zum Artikel schreiben]