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 objektorientierten Programmierung (OOP) ist die Vererbung. Am Beispiel eines Investments werden hier das OOP-Prinzip Vererbung und die darauf aufbauenden Konzepte (z.B. Überschreiben) erklärt. (Zuletzt geändert am 22.11.2024)
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 ganze Beispiel als Java-Code
Wer versucht hat, das ganze per UML geplante OOP-Programm in Java nachzuvollziehen, sollte etwa diesen Quelltext am Ende vor sich haben und ausführen können:
Eine beispielhafte Implementierung in Java könnte etwa so aussehen:
package de.oerinformatik.crm;
import java.util.ArrayList;
public class Konto{
private String iban;
protected double kontostand;
static ArrayList<Konto> kontenListe = new ArrayList<>();
public String getIban() {
return iban;
}
public void setIban(String iban) {
this.iban = iban;
}
void einzahlen(double betrag) {
kontostand = kontostand + betrag;
}
boolean auszahlen(double betrag) {
if (kontostand - betrag > 0){
kontostand = kontostand - betrag;
return true;
}else{
return false;
}
}
static String listeStatusAllerKonten(){
String ausgabe ="";
for (Konto einKonto: kontenListe){
ausgabe = ausgabe + "\n" + einKonto.kontostatus();
}
return ausgabe;
}
String kontostatus(){
return "KontoNr "+this.iban+" Kontostand: "+this.kontostand.toString();
}
Konto(String iban){
this.iban = iban;
kontostand =0.0;
kontenListe.add(this);
}
Konto(String ktn, String blz){
this.iban = "DE__"+blz+ktn;
kontenListe.add(this);
}
public double getKontostand() {
return kontostand;
}
}
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;
}
}
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);
}
}
}
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);
}
}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: 22.11.2024.
[Kommentare zum Artikel lesen, schreiben] / [Artikel teilen] / [gitlab-Issue zum Artikel schreiben]
