Abstrakte Klassen und Interfaces

https://bildung.social/@oerinformatik/

https://oer-informatik.de/uml-klassendiagramm-interface-abstraktion

tl/dr; (ca. 8 min Lesezeit): Ein großes Problem bei Objekt- und Klassenbeziehung ist die starke Kopplung unterschiedlicher Klassen aneinander. Der Austausch von Verhalten ist so nur mit großem Aufwand möglich. Das Konzept der Abstraktion bietet hier in Form von abstrakten Klassen und Interfaces die Möglichkeit, Komponenten lose zu koppeln. (Zuletzt geändert am 24.05.2023)

Dieser Text ist ein Teil der Infotext-Serie zu UML-Klassendiagrammen:

Abstrakte Klassen

Wozu abstrakte Klassen dienen soll an folgendem Beispiel erläutert werden: es soll das gesamte Vermögen, das in Aktien, auf Konten und als Festgeld angelegt ist, jederzeit ausgewertet werden können. Dazu sollen die Klassen Konto, Aktien und Festgeld von einer gemeinsamen Elternklasse Investment erben, in der festgelegt ist, dass wir über die Methode getGuthaben() immer den aktuellen Zeitwert des Investments erhalten.

Superklasse Konto
Superklasse Konto

Da das Guthaben zum Zeitwert beim Konto schlicht über den Kontostand, beim Festgeld über die Verzinsung der Einlage und bei einer Aktie über den aktuellen Börsenwert ermittelt werden muss, kann die Methode getGuthaben() nicht sinnvoll zentral in Investment implementiert werden. Wir können verdeutlichen, dass jede Unterklasse eine eigene Implementierung benötigt, in dem wir den Methodenrumpf leer lassen und lediglich die Signatur und der Rückgabewert festlegen: derlei Methoden nennt man abstrakt.

Durch die fehlende Implementierung von getGuthaben() ist das Verhalten eines Investment nicht vollständig definiert - es können keine konkreten Instanzen von Investment gebildet werden. Investment ist ebenso abstrakt.

Im UML-Klassendiagramm werden abstrakte Klassen und Methoden durch kursive Schreibweise gekennzeichnet:

Kursive Schreibweise bei Methode und Klassennamen
Kursive Schreibweise bei Methode und Klassennamen

Die einzelnen Kindklassen müssen die Methode getGuthaben() implementieren, damit sie selbst konkrete Klassen sind, die instanziiert werden können:

Implementierung der abstrakten Methoden in den Kindklassen
Implementierung der abstrakten Methoden in den Kindklassen

Die kursive Schreibweise kann schnell übersehen werden, daher ist es ratsam, zusätzlich das Schlüsselwort {abstract} als constraint in geschweiften Klammern unterhalb des Klassennamens zu notieren.

explizit per constraint als abstrakt gekennzeichnete Klasse
explizit per constraint als abstrakt gekennzeichnete Klasse

In der Regel werden zwar nur Klassen mit abstrakten Methoden selbst als abstrakt dargestellt, theoretisch kann jedoch jede Klasse als abstrakt gekennzeichnet werden, auch wenn sie instanziierbar wäre. Umgekehrt gilt jedoch:

Sobald eine Klasse über mindestens eine abstrakte Methode verfügt muss sie als abstrakt gekennzeichnet werden.

Alle Klassen, die nicht instanziiert werden können oder sollen, werden abstrakt genannt. Alle Klassen, die instanziiert werden können oder sollen, werden konkrete Klassen genannt.

Abstrakte Klassen können alle Member und Eigenschaften enthalten, die auch konkrete Klassen enthalten können.

Interfaces

Das Konzept der Abstraktion wird mit Interfaces weiterentwickelt: Interfaces stellen abstrakte Strukturen dar, die im engeren Sinn komplett frei von Implementierung sind. Im Kern legen Interfaces lediglich Methodensignaturen und Rückgabewerte fest.

Interfaces stellen Verträge zwischen Klassen dar, die festschreiben, welche Methoden eine Klasse implementieren muss.

Das Interface Verzinsbar legt fest, dass verrechneJahreszinsen() implementiert werden muss
Das Interface Verzinsbar legt fest, dass verrechneJahreszinsen() implementiert werden muss

Interfaces werden im UML-Diagramm wie Klassen dargestellt, die mit dem Stereotyp <<Interface>> oberhalb des Namens notiert werden.

Klassen, die die abstrakten des Interfaces implementieren, werden mit einer nicht ausgefüllten Pfeilspitze in Richtung Interface (analog der Vererbung) und einer gestrichelten Linie verbunden:

Die Klasse Sparbuch implementiert Verzinsbar (und somit verrechneJahreszinsen())
Die Klasse Sparbuch implementiert Verzinsbar (und somit verrechneJahreszinsen())

Sofern die Klassen die abstrakten Methoden eines Interfaces nicht selbst implementieren sind die Klassen selbst abstrakt.

Die Grenzen zu abstrakten Klassen sind in den unterschiedlichen Programmiersprachen unterschiedlich stark ausgeprägt. So können Interfaces auch Attribute beinhalten.

In Java gelten beispielsweise folgende Regeln:

  • Interfaces können statische Attribute enthalten

  • Interfaces können default-Implementierungen enthalten

Benutzen von Interfaces und Implementieren von Interfaces: Dependency Inversion Principle

Interfaces werden zur losen Kopplung zwischen Klassen genutzt. Häufig hängen Klassen über Assoziationen direkt von der Implementierung anderer Klassen ab. Im folgenden Beispiel benötigt die Klasse Kunde ein Objekt vom Typ Konto, um einen Artikel zahlen zu können:

Die Implementierung und Nutzung des Interfaces in Standard-Notation
Die Implementierung und Nutzung des Interfaces in Standard-Notation

Änderungen an Konto können somit unmittelbar dazu führen, dass sich auch Kunde ändern muss. Das ist insbesondere dann nicht wünschenswert, wenn sich beide in unterschiedlichen Modulen befinden (da Kunde Konto nutzt wäre es z.B. denkbar, dass sich dieses in einem Modul höheren Abstraktionsniveaus befindet).

An die Stelle der direkten Abhängigkeit tritt ein Vertrag zwischen Kunde und Konto: Die Abhängigkeit besteht darin, dass es die Methoden abbuchen() und einzahlen() geben muss: Kunde will diese Nutzen, und das Objekt, das Kunde nutzt, muss diese Methoden bereitstellen:

Die Implementierung und Nutzung des Interfaces in Standard-Notation
Die Implementierung und Nutzung des Interfaces in Standard-Notation

Solange das Interface unverändert bleibt, können die jeweiligen Implementierungen geändert, ja sogar getauscht werden: Kunde hängt nur noch von der abstrakten Struktur Buchbar ab, aber nicht mehr von einer konkreten Implementierung. Realisiert werden kann diese Abhängigkeit auch über andere Implementierungen von Buchbar.

Die Implementierung und Nutzung des Interfaces in Standard-Notation
Die Implementierung und Nutzung des Interfaces in Standard-Notation

Kunde nutzt ein Objekt vom Typ Buchbar, um einen Artikel zu bezahlen (siehe Auszug aus der Methode bezahlen()).

Lollipop- / Ball-and-Socket-Notation

Wenn der Aufbau des Interfaces nicht so wichtig ist wird häufig die Lollipop- (oder Ball and Socket-) Notation gewählt, die nur die Schnittstelle benennt, die Member des Interface aber nicht zeigt:

Die Implementierung und Nutzung des Interfaces in Standard-Notation
Die Implementierung und Nutzung des Interfaces in Standard-Notation

Bei Kunde ist mit der stilisierten Buchse gekennzeichnet, dass ein Buchbar-Objekt benötigt wird. Konto stellt dieses Objekt zur Verfügung, war durch den stilisierten Stecker dargestellt wird.


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: Abstrakte Klassen und Interfaces” von oer-informatik.de (H. Stein), Lizenz: CC BY 4.0. Der Artikel wurde unter https://oer-informatik.de/uml-klassendiagramm-interface-abstraktion 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: