Zum Inhalt springen
L

Wikipedia · einfach zusammengefasst · Stand

Vererbung (Programmierung)

Die Vererbung dient dazu, aufbauend auf existierenden Klassen neue zu schaffen, wobei die Beziehung zwischen ursprünglicher und neuer Klasse dauerhaft ist. Eine …

Inhalt6 Abschnitte
  1. 1. Grundidee und Begriffe
  2. 2. Anwendungen und grundlegende Varianten
  3. 3. Fahrzeug- und Geometriebeispiele
  4. 4. Substitution, Methoden und Typkonvertierung
  5. 5. Besondere Formen und technische Regeln
  6. 6. Wartung und prototypenbasierte Vererbung

Grundidee und Begriffe

Vererbung ist ein Grundkonzept der objektorientierten Programmierung. Auf Grundlage einer bestehenden Klasse entstehen neue Klassen, die deren Datentyp und Funktionalität übernehmen, erweitern, einschränken oder anpassen können. Die Beziehung bleibt dauerhaft bestehen und dokumentiert strukturelle sowie verhaltensbezogene Ähnlichkeiten. Dadurch entstehen Klassenhierarchien.

Die vererbende Klasse heißt Basisklasse, Superklasse, Oberklasse oder Elternklasse. Die erbende Klasse wird abgeleitete Klasse, Subklasse, Unterklasse oder Kindklasse genannt. Das Bilden einer spezielleren Klasse heißt Ableitung oder Spezialisierung; die umgekehrte Betrachtung heißt Generalisierung. Typischerweise besteht eine „ist-ein“-Beziehung: Ein Objekt der abgeleiteten Klasse ist zugleich ein Objekt des Basistyps.

In der Unified Modeling Language (UML) zeigt ein Pfeil mit dreieckiger Spitze von der abgeleiteten Klasse zur Basisklasse. Geerbte Attribute und Methoden werden in der abgeleiteten Klasse nicht nochmals aufgeführt. Klassen können sowohl einen Typ mit seiner Schnittstelle als auch eine Implementierung festlegen. Einige Sprachen unterscheiden deshalb zwischen Schnittstelle (Interface) und Klasse. Erbt eine Klasse direkt von mehreren Basisklassen, liegt Mehrfachvererbung vor.

Vererbung unterstützt eine anschauliche Modellierung, Wiederverwendung und modulare Erweiterung von Software. Ihr sinnvoller Umfang ist jedoch umstritten: Besonders Vererbung, die hauptsächlich der Wiederverwendung von Code dient, kann die Softwarequalität auch beeinträchtigen.

Anwendungen und grundlegende Varianten

Wichtige Anwendungsformen sind:

  • Bei der Subtyp-Vererbung ist die abgeleitete Klasse ein Subtyp der Basisklasse. Ein Objekt des Subtyps muss überall einsetzbar sein, wo ein Objekt des Basistyps erwartet wird. Seine möglichen Ausprägungen bilden eine Teilmenge der Ausprägungen des Basistyps.
  • Vererbung zur Erweiterung ergänzt Attribute oder Funktionalität. Methoden können überschrieben, also in einer Spezialisierung neu implementiert werden. Eine abstrakte Klasse kann außerdem nur einen Teil der benötigten Funktionalität umsetzen und den Rest weiteren Spezialisierungen überlassen.
  • Allgemeine Fähigkeiten wie Serialisierbarkeit oder Vergleichbarkeit können durch abstrakte Klassen beziehungsweise Schnittstellen wie Serializable oder Comparable verlangt werden. Die abgeleiteten Klassen implementieren die Anforderungen.
  • Standardimplementierungen können in zentralen Mixin-Klassen bereitgestellt werden. Ein Mixin steuert wiederverwendbare Funktionalität zu anderen Klassen bei.

Vererbung eignet sich nicht für jede Beziehung. Eine übermäßig tiefe Hierarchie für einfache Eigenschaften erschwert das Modell. Ebenso darf eine „hat“-Beziehung nicht fälschlich als „ist-ein“-Beziehung dargestellt werden: Apfelkuchen ist zwar ein Kuchen, aber kein Apfel. Hier wäre Aggregation, also das Zusammensetzen eines Objekts aus anderen Objekten, angemessener.

Auch wechselnde oder gleichzeitig bestehende Rollen passen schlecht in eine starre Klassenhierarchie. Werden Kunde und Mitarbeiter als Spezialisierungen von Person modelliert, kann eine Person programmtechnisch nicht ohne Weiteres ihre Rolle wechseln oder beide Rollen zugleich besitzen. Üblich ist deshalb, Person und Rollen getrennt zu modellieren und durch Beziehungen zu verbinden.

Typ und Implementierung können gemeinsam oder getrennt vererbt werden:

  • Implementierungsvererbung beziehungsweise Subclassing übernimmt Implementierung und Schnittstelle. Attribute und Methoden können ergänzt oder verändert werden; spätere Verbesserungen der Basisklasse wirken auch auf ihre Unterklassen.
  • Schnittstellenvererbung beziehungsweise Subtyping übernimmt den Typ, ohne eine bestimmte Implementierung vorzuschreiben.
  • Reine Implementierungsvererbung übernimmt Funktionalität, ohne dass die erbende Klasse nach außen als Untertyp gilt. Sie wird auch private Vererbung genannt, ist etwa in C++ und Eiffel möglich, aber nicht direkt in Java oder .NET-Sprachen. Dort kann Delegation verwendet werden.

Java und .NET-Sprachen trennen Interfaces und Klassen formal, aber nicht vollständig: Implementiert eine Klasse ein Interface, erben weitere Spezialisierungen sowohl Schnittstelle als auch Implementierung.

Fahrzeug- und Geometriebeispiele

Im Fahrzeugbeispiel besitzt die Basisklasse Fahrzeug die Attribute Fahrzeugnummer, Leergewicht und ZulässigesGesamtgewicht. Kraftfahrzeug und Fahrrad übernehmen sie und ergänzen eigene Angaben. Ein Kraftfahrzeug enthält zusätzlich Höchstgeschwindigkeit und Leistung; ein Fahrrad beispielsweise die Rahmenhöhe. Weitere Spezialisierungen sind Motorrad, PKW und LKW. Ein PKW ergänzt die AnzahlSitzplätze, ein LKW die Nutzlast.

Die Methode PruefeVerfuegbarkeit der Klasse Fahrzeug kann anhand der Fahrzeugnummer, etwa durch einen Datenbankzugriff, die Verfügbarkeit ermitteln. Alle Spezialisierungen erben sie. Auch später ergänzte Methoden stehen ihnen grundsätzlich zur Verfügung. PrüfeFahrerlaubnis wird bei Kraftfahrzeug eingeführt. Manche Fälle lassen sich anhand von zulässigem Gesamtgewicht, Höchstgeschwindigkeit und Leistung behandeln; andere benötigen Informationen der Spezialisierungen, etwa die Sitzplatzzahl eines PKW. Deshalb muss die Methode dort überschrieben werden.

Zur Prüfung wird das konkrete Fahrzeug instantiiert, also als Objekt seiner Spezialisierung erzeugt. Ein Objekt der Fahrerlaubnis wird der Prüfmethode übergeben. Im Speicher liegen geerbte Attribute typischerweise hintereinander. Zusätzlich kann das Objekt einen Zeiger auf eine Tabelle virtueller Methoden enthalten. Diese Tabelle bestimmt bei einem Methodenaufruf, welche konkrete, gegebenenfalls überschriebene Implementierung ausgeführt wird.

Ein weiteres Beispiel leitet in Java Quadrat von Rechteck ab. Rechteck speichert laenge und breite und berechnet den Umfang als 2 · laenge + 2 · breite sowie die Diagonale als √(laenge² + breite²). Der Quadrat-Konstruktor übergibt dieselbe Länge zweimal an den Konstruktor der Basisklasse. Die Umfangsberechnung wird geerbt; die Diagonale kann mit √2 · laenge überschrieben werden.

Das Beispiel zeigt zugleich ein Problem: Erhält Rechteck später setLaenge und setBreite, muss Quadrat seine Seiten gleich halten. Verdoppelt eine Anwendung die Länge eines vermeintlichen Rechtecks, erwartet sie eine Verdopplung der Fläche. Bei einem Quadrat wird wegen der gleichzeitigen Breitenänderung die Fläche jedoch vervierfacht.

Substitution, Methoden und Typkonvertierung

Das liskovsche Substitutionsprinzip verlangt, dass ein Subtyp sich so verhält, dass die Verwendung als Basistyp nicht zu unerwartetem Verhalten führt. Programmiersprachen können Verletzungen dieses Prinzips durch Polymorphie nicht allgemein verhindern. Abstrakte Klassen und Schnittstellen nahe der Wurzel einer Hierarchie helfen, Typbeschreibung und Implementierungsdetails besser zu trennen.

Beim Überschreiben soll eine Methode oft die allgemeine Implementierung der Basisklasse weiterverwenden und lediglich ergänzen. Die Basismethode wird dann am Anfang, am Ende oder zwischen zusätzlichen Verarbeitungsschritten aufgerufen. C++ erlaubt über Klassenname und Scope-Operator den gezielten Aufruf von Methoden der Hierarchie. Java verwendet super. In CLOS ruft call-next-method die nächste Implementierung auf und übernimmt die aktuellen Parameter automatisch.

Im Fahrzeugbeispiel kann PrüfeFahrerlaubnis in PKW zunächst die Implementierung von Kraftfahrzeug aufrufen. Diese kann wiederum die Prüfung des zulässigen Gesamtgewichts an Fahrzeug weitergeben. Nach der Rückkehr setzt jede Stufe ihre speziellere Prüfung fort, sofern noch keine Entscheidung möglich war.

Bei den Signaturen überschriebener Methoden entspricht dem Substitutionsprinzip Kontravarianz für Eingangsparameter und Kovarianz für Ausgangsparameter: Eingangsparameter dürfen allgemeiner, Rückgabewerte spezieller als in der Basisklasse sein. Viele Sprachen verlangen allerdings identische Parametertypen. In Java und Modula-3 muss die Menge möglicher deklarierter Ausnahmetypen einer überschriebenen Methode eine Teilmenge der ursprünglichen Menge sein. Beim Design by Contract in Eiffel dürfen Nachbedingungen und Klasseninvarianten gleich oder restriktiver werden; Vorbedingungen dürfen gelockert werden.

Bei statischer Typisierung kann die Verträglichkeit von Typen bereits beim Übersetzen geprüft werden. Ein Objekt einer Spezialisierung darf einer Variable des Basistyps zugewiesen werden. Dieser Upcast ist beispielsweise von PKW zu Kraftfahrzeug möglich. Spezielle Eigenschaften wie AnzahlSitzplätze sind danach nicht direkt zugänglich, virtuelle Methoden verhalten sich aber weiterhin gemäß der konkreten Klasse.

Der umgekehrte Downcast muss normalerweise ausdrücklich verlangt und zur Laufzeit geprüft werden. Eine Konvertierung von Kraftfahrzeug zu PKW ist nur korrekt, wenn das Objekt tatsächlich ein PKW ist. Wird stattdessen ein LKW umgewandelt, entsteht im Regelfall eine Ausnahme.

Besondere Formen und technische Regeln

Mehrfachvererbung liegt vor, wenn eine Klasse direkt von mehreren Basisklassen erbt; eine Kette aus mehreren Stufen zählt nicht dazu. Ein Zweiwegefahrzeug kann beispielsweise zugleich von Kraftfahrzeug und Schienenfahrzeug erben und zusätzlich die Spurweite erhalten. C++, Eiffel und Python unterstützen Mehrfachvererbung umfassend. Java und .NET erlauben viele Interfaces, aber nur eine implementierende Basisklasse. Ruby erlaubt eine direkte Basisklasse und beliebig viele Modules.

Probleme sind Namenskollisionen und das mehrfache Auftreten derselben indirekten Basisklasse. Letzteres heißt Diamond-Problem. Ein Zweiwegefahrzeug könnte eine oder zwei Instanzen von Fahrzeug enthalten. C++ regelt dies mit virtuellen Basisklassen; Eiffel kann die Entscheidung sogar für einzelne Attribute und Methoden treffen. Python verwendet dafür seit Version 2.3 die C3-Linearisierung.

Multilevel-Vererbung bezeichnet eine Kette aus mindestens drei Klassen. Sie kann mit Mehrfachvererbung verbunden werden und eignet sich besonders für Rapid-Prototyping, weil Code auf getrennte Klassen verteilt und dennoch zusammengeführt werden kann.

Die Datenkapselung steuert die Sichtbarkeit:

  • public: überall sichtbar,
  • protected: in der Klasse und ihren auch mehrstufig abgeleiteten Klassen sichtbar, aber nicht von außen,
  • private: nur in der Klasse selbst sichtbar.

Java und .NET besitzen zusätzliche Sichtbarkeiten für Package beziehungsweise Assembly. C++ und Eiffel können Sichtbarkeiten beim Erben einschränken; Java und .NET nicht. Ruby versteht private anders: Auch Spezialisierungen können darauf zugreifen, grundsätzlich aber nur bei derselben Instanz.

Viele Sprachen besitzen eine zentrale Basisklasse: In Ruby, Java und .NET heißt sie Object, in Eiffel ANY. C++ und Python gehören zu den Ausnahmen. Die zentrale Klasse stellt allgemeine Funktionen und einen Typ für beliebige Objekte bereit. Allgemeine Container können dadurch jedoch Downcasts und Laufzeitprüfungen erfordern.

Bei der Datenbankpersistenz unterscheidet man Single Table Inheritance mit einer Tabelle für die gesamte Hierarchie, Class Table Inheritance mit einer Tabelle je Klasse und Concrete Table Inheritance mit einer Tabelle je nicht abstrakter Klasse. Bei Single Table Inheritance wird der Objekttyp zusätzlich gespeichert; unpassende Attributspalten enthalten Null-Werte. Objektorientierte Datenbanken unterscheiden außerdem Persistenz durch Vererbung, bei der der Typ über Persistenz entscheidet, und orthogonale Persistenz, bei der Objekte derselben Klasse persistent oder transient sein können.

Wartung und prototypenbasierte Vererbung

Vererbung kann Entwürfe verständlich, Bibliotheken anpassbar und große Systeme modular machen. Zu tiefe Hierarchien erhöhen jedoch die Komplexität und erschweren Änderungen.

Besonders riskant ist die nachträgliche Änderung einer zentralen Klassenschnittstelle. Wird einer abstrakten Basisklasse eine neue virtuelle Methode ohne Implementierung hinzugefügt, müssen alle Spezialisierungen sie umsetzen. Existiert weiter unten bereits zufällig eine gleichnamige Methode, kann sie in einem unpassenden Kontext aufgerufen werden, ohne dass der Compiler dies erkennt. Wird eine virtuelle Methode umbenannt oder ihre Signatur geändert, müssen alle überschreibenden Methoden angepasst werden. Andernfalls werden sie möglicherweise nicht mehr aufgerufen und eine ungeeignete Standardimplementierung tritt an ihre Stelle. C# verlangt für Überschreibungen das Schlüsselwort override und kann dadurch bestimmte Fehler erkennen.

Das Fragile Base Class Problem bezeichnet Schäden in Unterklassen durch Änderungen einer Basisklasse, obwohl deren Schnittstelle unverändert bleibt. Ursache sind häufig ungenaue Verhaltensverträge oder offengelegte Implementierungsdetails, auf die Unterklassen unbeabsichtigt angewiesen sind. In Java sind Methoden grundsätzlich virtuell; soll Überschreiben verhindert werden, müssen sie als final markiert werden.

In prototypenbasierten Sprachen gibt es keine Trennung zwischen Klasse und instanziiertem Objekt. Ein durch Cloning erzeugtes Child übernimmt vom Parent daher nicht nur die Struktur, sondern auch Inhalte. Die Methodennutzung über den Parent entspricht eher Delegation als klassischer Klassenvererbung. Sie ist flexibler; in manchen Sprachen kann der Empfänger der Delegation und damit die funktionale „Basisklasse“ sogar zur Laufzeit ausgetauscht werden.

Lernvideos zu Vererbung (Programmierung)

Weiterlesen

Unified Modeling Language Die UML ist die dominierende Sprache für die Softwaresystem-Modellierung. Der erste Kontakt zur UML besteht häufig darin, dass Diagramme in UML im Rahmen der … Objektorientierung Unter Objektorientierung (kurz OO) versteht man in der Softwaretechnik eine Sichtweise auf komplexe Systeme, bei der ein System durch das Zusammenspiel … Klasse (Objektorientierung) Die Klasse dient als Bauplan für die Abbildung von realen Objekten in Softwareobjekte und beschreibt Attribute (Eigenschaften) und Methoden (Verhaltensweisen) … Abgeleitete Klasse Die abgeleitete Klasse erbt alle Attribute (Member) und Methoden der Basisklasse, kann dabei aber nur auf die nicht als privat deklarierten direkt zugreifen. Generalisierung (UML) Generalisierung (engl. Generalization) ist ein Modellelement in der Unified Modeling Language (UML), einer Modellierungssprache für Software und andere … Modellierungssprache Jede Modellierungssprache verfügt über eine festgelegte Syntax, die über eine Grammatik oder ein Metamodell beschrieben werden kann. Betrachtet man die immer … Attribut (Programmierung) Ein Attribut (von lateinisch attribuere ‚zuteilen', ‚zuordnen'), auch Eigenschaft genannt, gilt im Allgemeinen als Merkmal, Kennzeichen, Informationsdetail … Methode (Programmierung) Methoden (englisch method oder member function) sind in der objektorientierten Programmierung Unterprogramme in der Form von Funktionen oder Prozeduren, … Objektorientierte Programmierung Die objektorientierte Programmierung (kurz OOP) ist ein auf dem Konzept der Objektorientierung basierendes Programmierparadigma. Die Grundidee besteht darin … Komponentenbasierte Entwicklung Grundlage dieses Konzeptes sind Software-Komponenten, die die Wiederverwendbarkeit von Software-Artefakten verbessern sollen. Diagramm über die Entwicklung von … Schnittstelle (Objektorientierung) Eine Schnittstelle oder ein Interface (englisch interface oder protocol) definiert in der objektorientierten Programmierung, welche Methoden in den … Klassendiagramm Ein Klassendiagramm ist ein Strukturdiagramm der Unified Modeling Language (UML) zur grafischen Darstellung (Modellierung) von Klassen, Schnittstellen sowie …