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.

Die Klassen PrivatKonto und Geschäftskonto erben von Konto
Die Klassen PrivatKonto und Geschäftskonto erben von Konto

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:

Die Klassen Konto im Detail
Die Klassen Konto im Detail

Als Java-Quelltext könnte die Klasse beispielsweise so aussehen:

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:

Die Klassen PrivatKonto im Detail
Die Klassen PrivatKonto im Detail

Java-Quelltext für diese Klasse sähe beispielsweise so aus:

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

Die Klassen GeschäftsKonto im Detail
Die Klassen GeschäftsKonto im Detail

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:

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: #).

Das Attribut Kontostand wird auf protected gesetzt
Das Attribut Kontostand wird auf protected gesetzt

Im 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:

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:

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.

In der Liste kontenListe werden alle Instanzen von Konto + Subklassen verwaltet
In der Liste 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):

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:

Für GeschäftsKonto:

Für SparKonto:

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]

Kommentare gerne per Mastodon, Verbesserungsvorschläge per gitlab issue (siehe oben). Beitrag teilen: