- Häufige Fragen im Fachgespräch – Objektorientierung – Anwendungsentwickler-Podcast #2
- Häufige Fragen im Fachgespräch – Kapselung – Anwendungsentwickler-Podcast #6
- Häufige Fragen im Fachgespräch – Vererbung – Anwendungsentwickler-Podcast #8
- Häufige Fragen im Fachgespräch – Polymorphie – Anwendungsentwickler-Podcast #10
- Häufige Fragen im Fachgespräch – Programmierung (Teil 1) – Anwendungsentwickler-Podcast #12
- Häufige Fragen im Fachgespräch – Programmierung (Teil 2) – Anwendungsentwickler-Podcast #13
- Relationale Datenbanken – Häufige Fragen im Fachgespräch – Anwendungsentwickler-Podcast #17
- Normalisierung – Häufige Fragen im Fachgespräch – Anwendungsentwickler-Podcast #18
- SQL – Häufige Fragen im Fachgespräch – Anwendungsentwickler-Podcast #19
- Erläuterung der Gehaltsabrechnung – Häufige Fragen im Fachgespräch – Anwendungsentwickler-Podcast #46
- Zusammensetzung des Stundensatzes – Häufige Fragen im Fachgespräch – Anwendungsentwickler-Podcast #47
- Testverfahren für Software – Häufige Fragen im Fachgespräch – Anwendungsentwickler-Podcast #94
- Unit-Tests – Häufige Fragen im Fachgespräch – Anwendungsentwickler-Podcast #95
- Rückblick auf die Fachgespräche der Sommerprüfung 2022 (Fachinformatiker:in Anwendungsentwicklung) – IT-Berufe-Podcast #177
- Handelskalkulation, Skonto und Rabatt – Häufige Fragen im Fachgespräch – Anwendungsentwickler-Podcast #148
In der achten Episode meines Anwendungsentwickler-Podcasts setze ich meine Reihe der häufigsten Prüfungsfragen im Fachgespräch fort. Das Thema ist dieses Mal die zweite „Säule“ der Objektorientierung, die Vererbung.
TL;DR
Für dein Fachgespräch empfehle ich dir, Vererbung anhand eines selbst umgesetzten Beispiels zu erklären: Eine Subklasse übernimmt zugängliche Attribute und Methoden ihrer Basisklasse und kann Methoden überschreiben. Dazu gehören Kenntnisse über abstrakte Klassen, Methodensignaturen und die Schlüsselwörter deiner Programmiersprache. Mit dem Entwurfsmuster Template Method lässt sich zeigen, wie eine Basisklasse einen Ablauf festlegt, dessen einzelne Schritte Subklassen implementieren. Beim Programmieren solltest du bevorzugt Komposition oder Delegierung prüfen und bei Vererbung das Liskov Substitution Principle einhalten, damit Subklassen ihre Basisklasse ersetzen können.
Kapitelmarken
- [00:00:01] Einstieg ins Fachgespräch
- [00:00:37] Vererbung als Thema der mündlichen Prüfung
- [00:01:31] Eine Antwort vom Allgemeinen zum Detail aufbauen
- [00:02:07] Basisklasse, Subklasse und Vererbungssyntax
- [00:02:48] Sichtbarkeit von Attributen und Methoden
- [00:03:11] Abstrakte Klassen und Methoden
- [00:04:22] Methoden überschreiben und Vererbung begrenzen
- [00:05:24] Zugriff auf die Basisklasse mit super
- [00:05:39] Mehrfachvererbung und Diamantenproblem
- [00:06:24] Object als gemeinsame Basisklasse
- [00:06:43] Die eigene Programmiersprache im Fachgespräch
- [00:07:34] Ein Beispiel aus dem eigenen Projekt vorbereiten
- [00:08:50] Die Methodensignatur
- [00:10:33] Methoden überladen und überschreiben
- [00:13:13] Private Methoden in Subklassen
- [00:14:01] Das Entwurfsmuster Template Method
- [00:16:32] Template Method beim Schreiben einer XML-Datei
- [00:17:30] Vererbung in der Praxis
- [00:18:17] Enge Klassenbeziehungen und Komposition
- [00:19:32] Das Liskov Substitution Principle
- [00:20:56] Quadrat und Rechteck als problematische Klassenbeziehung
- [00:23:53] Ausblick auf Polymorphie und weiterführende Quellen
Beantwortung der Frage im Fachgespräch
- Klassen können Attribute und Methoden von anderen Klassen übernehmen, indem sie von ihnen erben.
- Die vererbende Klasse heißt Basisklasse (auch Eltern- oder Superklasse) und die erbende Klasse Subklasse (auch Kind- oder abgeleitete Klasse).
- Um von einer Klasse zu erben, schreibt man in Java
extendsund den Namen des Basisklasse hinter die Klassendefinition. In C# wird anstattextendseinfach der Doppelpunkt:verwendet. - Alle Attribute und Methoden der Basisklasse können automatisch in der Subklasse verwendet werden, sofern sie für diese sichtbar sind. Der Sichtbarkeitsmodifizierer
protectederlaubt der Klasse selbst und allen Subklassen den Zugriff auf die mit ihm ausgezeichneten Member. - Eine Klasse kann mit
abstractals nicht instantiierbar gekennzeichnet werden. Dann können ihre Attribute und Methoden nur über Objekte ihrer Subklassen aufgerufen werden. - Auch eine Methode kann als
abstractgekennzeichnet werden. Sie wird dann in der Basisklasse nicht implementiert und muss in den Subklassen implementiert werden. Sobald mind. eine Methode abstrakt ist, muss auch die gesamte Klasse abstrakt sein. - Methoden können in Subklassen überschrieben werden. Das bedeutet, dass in der Subklasse eine Methode mit derselben Signatur (Methodenname und Parameter) wie in der Basisklasse implementiert wird. Beim Aufruf der Methode an einem Objekt der Subklasse wird nun deren Implementierung verwendet anstatt der der Basisklasse.
- Java erlaubt standardmäßig das Überschreiben von Methoden, während C# es standardmäßig verbietet. In Java kann man Methoden mit
finalals nicht überschreibbar kennzeichnen und in C# mitvirtualals überschreibbar. - Auch Klassen können als nicht überschreibbar gekennzeichnet werden, was bedeutet, dass man von ihnen nicht erben kann. In Java wird hierfür
finalverwendet und in C#sealed. - Mit
superkann innerhalb der Subklasse auf Member der Basisklasse zugegriffen werden. - In den meisten neueren Programmiersprachen ist es nur möglich, von einer einzigen Klasse zu erben, da die Mehrfachvererbung (die z.B. in C++ erlaubt ist) zu Problemen führen kann (z.B. dem Diamantenproblem).
- In Java erben alle Klassen automatisch von
Object.
Weiterführende Diskussion
- Legt euch ein interessantes Beispiel (bitte nicht das alte Auto/Fahrzeug) für die Vererbung für die Prüfung zurecht.
- Was ist die Methodensignatur? (Methodenname und Reihenfolge und Typ der Parameter)
- Was ist der Unterschied zwischen dem Überladen und dem Überschreiben von Methoden? (Überschreiben: gleiche Signatur in Subklasse, Überladen: gleicher Name in derselben Klasse)
- Was passiert, wenn ich eine
privateMethode „überschreibe“? (sie wird in der Subklasse „maskiert„) - Design Pattern „Template Method“ (verwendet
finalundabstract)
Tipps für die Entwicklerpraxis
- Viele Entwurfsmuster verwenden Vererbung.
- Vererbung nicht im Sinne der „ist-ein“-Beziehung verstehen, sondern zur reinen Wiederverwendung.
- Beim Einsatz von Vererbung immer das Liskov Substitution Principle einhalten. Gegenbeispiel: Ein Quadrat ist kein Rechteck.
- „Favor composition over inheritance.„
Literaturempfehlungen
*
(direkt beim Rheinwerk-Verlag bestellen*)- Das passende Kapitel im OpenBook: 5.8 Vererbung – Rheinwerk Computing :: Java ist auch eine Insel
*
*
*
*
Links
- Permalink zu dieser Podcast-Episode
- RSS-Feed des Podcasts
- Anwendungsentwickler-Podcast #2: Häufige Fragen im Fachgespräch – Objektorientierung
- Anwendungsentwickler-Podcast #6: Häufige Fragen im Fachgespräch – Kapselung
- Rheinwerk Computing :: Java ist auch eine Insel – 5.8 Vererbung
- Can we override private method in Java – Example program | Java67
- The Liskov Substitution Principle
- Multiple inheritance: The diamond problem – Wikipedia, the free encyclopedia
- Software design pattern – Wikipedia, the free encyclopedia
- Template method pattern – Wikipedia, the free encyclopedia
Weitere Infos zum Fachgespräch
Du suchst noch mehr Tipps rund um das Fachgespräch? Dann schau doch mal in diese Artikel- und Podcast-Kategorie: Alle Artikel rund um das Fachgespräch.
Kennst du schon meine Hörbücher zur Vorbereitung auf das Fachgespräch? Unter dasperfektefachgespraech.de kannst du sie herunterladen. In insg. über 10 Stunden gehe ich über 200 Fragen im Detail durch und gebe Tipps für die Beantwortung.
Und wenn du dich für meinen Newsletter einträgst, bekommst du immer direkt alle Neuigkeiten von dieser Seite in dein Postfach geliefert.
Jetzt anmelden!
Zusammenfassung
Vererbung im Fachgespräch erklären
Für deine Antwort im Fachgespräch empfehle ich dir, mit einer allgemeinen Definition zu beginnen und anschließend die Details anhand der Programmiersprache zu erläutern, mit der du im Betrieb arbeitest. Ein Beispiel aus deinem eigenen Projekt hilft dir, deine Erfahrungen einzubringen und zu begründen, weshalb du dich für Vererbung entschieden hast.
Durch Vererbung übernimmt eine Klasse Attribute und Methoden einer anderen Klasse, soweit diese für sie zugänglich sind. Die vererbende Klasse heißt Basisklasse, Elternklasse oder Superklasse. Für die erbende Klasse sind die Begriffe Subklasse, abgeleitete Klasse und Kindklasse gebräuchlich.
In Java steht dafür extends hinter dem Klassennamen, beispielsweise class Auto extends Fahrzeug. C# verwendet einen Doppelpunkt.
Sichtbarkeit der geerbten Member
Ob du auf Attribute und Methoden der Basisklasse zugreifen kannst, hängt von ihrer Sichtbarkeit ab:
publicerlaubt den Zugriff aus allen Klassen.protectederlaubt den Zugriff innerhalb der definierenden Klasse und ihrer abgeleiteten Klassen.privatebeschränkt den direkten Zugriff auf die definierende Klasse.
Mit super kannst du in Java innerhalb einer Subklasse auf zugängliche Member der Basisklasse zugreifen. Über super() lässt sich beispielsweise deren Konstruktor aufrufen.
Abstrakte Klassen und Methoden
Eine mit abstract gekennzeichnete Klasse dient als Grundlage für Subklassen. Von ihr selbst kannst du kein Objekt erzeugen. Das passt zu Klassen, die gemeinsame Funktionalität bereitstellen und erst durch ihre Subklassen konkret werden.
Bei einer abstrakten Methode enthält die Basisklasse keine Implementierung. Konkrete Subklassen müssen diese bereitstellen. Sobald eine Klasse mindestens eine abstrakte Methode enthält, muss auch die Klasse als abstrakt gekennzeichnet sein.
Methoden überschreiben und Vererbung begrenzen
Beim Überschreiben implementiert eine Subklasse eine Methode mit derselben Signatur wie in der Basisklasse. Wenn du die Methode an einem Objekt dieser Subklasse aufrufst, wird die Implementierung der Subklasse ausgeführt.
Für Java und C# werden folgende Schlüsselwörter behandelt:
| Sprache | Methoden | Klassen |
|—|—|—|
| Java | final verhindert das Überschreiben einer Methode. | Eine mit final gekennzeichnete Klasse erlaubt keine Subklassen. |
| C# | virtual kennzeichnet eine Methode als überschreibbar. | sealed schließt das Ableiten weiterer Klassen aus. |
Mehrfachvererbung und Diamantenproblem
In vielen modernen Programmiersprachen kann eine Klasse von genau einer Klasse erben. C++ erlaubt auch Mehrfachvererbung.
Beim Diamantenproblem erben zunächst zwei Klassen von derselben Basisklasse. Eine weitere Klasse erbt anschließend von diesen beiden Klassen. Dadurch können Member über mehrere Wege bei derselben Klasse ankommen, sodass ihre Zuordnung mehrdeutig wird.
Viele objektorientierte Sprachen haben außerdem eine gemeinsame übergeordnete Klasse. In Java erben alle anderen Klassen automatisch direkt oder indirekt von Object.
Ein eigenes Beispiel vorbereiten
Ich empfehle dir, vor der Prüfung ein Beispiel aus deinem Projekt im Detail durchzugehen. Damit kannst du erläutern, welche Vorteile und Nachteile sich ergeben haben und weshalb die Beziehung zwischen deinen Klassen sinnvoll ist.
Das häufig verwendete Beispiel mit Auto und Fahrzeug kann bei Nachfragen Schwierigkeiten bereiten, wenn du es nur aus dem Unterricht kennst. Mit einem selbst umgesetzten Beispiel hast du konkrete Erfahrungen, auf die du als angehende:r Fachinformatiker:in Anwendungsentwicklung zurückgreifen kannst.
Methodensignatur und Überladen
Zur Methodensignatur gehören der Methodenname sowie die Typen und die Reihenfolge der Parameter. Parameternamen und Rückgabetyp zählen dabei nicht zur Signatur.
Wenn du in Java zwei Methoden mit demselben Namen und denselben Parametertypen in derselben Reihenfolge definierst, gelten sie als dieselbe Methode. Ein anderer Rückgabetyp oder anders benannte Parameter ergeben keine zusätzliche Signatur.
Beim Überladen definierst du innerhalb einer Klasse weitere Methoden mit demselben Namen und unterschiedlichen Parametern. So lässt sich dieselbe Funktionalität für verschiedene Datentypen anbieten. Ein Beispiel ist System.out.println in Java.
Auch eine Methode zum Addieren lässt sich überladen: Eine Variante verarbeitet zwei int-Werte, eine weitere zwei double-Werte. Dabei können die Rückgabetypen passend zu den jeweiligen Parametern ebenfalls unterschiedlich sein.
Private Methoden
Private Methoden der Basisklasse sind für die Subklasse unsichtbar und daher nicht überschreibbar. Du kannst in der Subklasse eine eigene Methode mit derselben Signatur anlegen. Die private Methode der Basisklasse behält dabei ihre Funktionalität.
Template Method als Beispiel für ein Entwurfsmuster
Das Entwurfsmuster Template Method, auch Vorlagen- oder Schablonenmethode genannt, legt einen Algorithmus als feste Abfolge einzelner Schritte fest. In Java kann die Methode, die diesen Ablauf steuert, mit final gekennzeichnet werden. Die einzelnen Schritte werden als abstrakte Methoden definiert, die konkrete Subklassen implementieren müssen.
Beim Kuchenbacken werden beispielsweise Zutaten in einen Topf gegeben, bevor sie verrührt werden. Danach kommt die Mischung auf das Backblech. Die übergeordnete Methode hält diese Reihenfolge fest, während die Subklassen bestimmen, wie die einzelnen Schritte ausgeführt werden.
Ich habe dieses Muster beim Schreiben einer XML-Datei eingesetzt. Die Basisklasse legt fest, dass zuerst der Kopf geschrieben wird. Auf die Elemente folgt anschließend der Fuß. Für diese Schritte gibt es abstrakte Methoden, deren konkrete Inhalte die jeweilige Subklasse bereitstellt. Die mit final gekennzeichnete Methode zum Schreiben der gesamten XML-Datei wird unverändert übernommen.
Vererbung in der Praxis einsetzen
Viele objektorientierte Sprachen und Entwurfsmuster arbeiten mit Vererbung. Sie schafft allerdings eine enge Beziehung zwischen Klassen. Wenn du später eine andere Basisklasse brauchst, kann das größere Änderungen erfordern, weil bislang geerbte Methoden anderweitig bereitgestellt werden müssen.
Bevor ich Klassen über Vererbung verbinde, prüfe ich deshalb, ob Komposition oder Delegierung die Aufgabe lösen können. Der Merksatz dazu lautet: Favor composition over inheritance. Dabei arbeitet eine Klasse mit einer anderen Klasse zusammen beziehungsweise überträgt ihr Aufgaben.
Liskov Substitution Principle
Das Liskov Substitution Principle gehört zu den fünf SOLID-Prinzipien. Eine Subklasse muss überall dort einsetzbar sein, wo ihre Basisklasse erwartet wird. Dazu muss sie deren Schnittstelle bereitstellen und die damit verbundenen Erwartungen erfüllen. Einschränkungen geerbter Funktionen können diese Ersetzbarkeit verletzen.
Das Beispiel Quadrat und Rechteck
Mathematisch ist ein Quadrat eine besondere Form eines Rechtecks. Für eine programmierte Klasse mit veränderbaren Seitenlängen ergeben sich daraus Schwierigkeiten.
Eine Rechteckklasse kann zwei unabhängig veränderbare Seitenlängen a und b besitzen. Erbt eine Quadratklasse diese Attribute und die zugehörigen Setter, muss sie dafür sorgen, dass beide Werte immer gleich bleiben. Beim Setzen einer Seite müsste sie deshalb auch die andere ändern.
Dadurch verändert sich das Verhalten der geerbten Setter: Code, der mit einem Rechteck arbeitet, erwartet unabhängig veränderbare Seitenlängen. Ein Quadrat mit gekoppelten Seitenlängen erfüllt diese Erwartung nicht. Werden die Werte beim Ändern zudem nicht zuverlässig angeglichen, entstehen inkonsistente Zustände.
Transkription der gesamten Episode
Automatisch erzeugte Transkription der Episode
Hi, hier ist Stefan. In dieser Podcast-Episode geht es ums Fachgespräch.
Wenn du noch mehr Informationen zum Fachgespräch von mir haben willst,
dann schau doch mal auf meiner Website vorbei, dasperfektefachgespräch.de.
Und jetzt wünsche ich dir viel Spaß mit der Podcast-Episode.
Herzlich willkommen zum Anwendungsentwickler-Podcast,
dem Podcast rund um die Ausbildung zum Fachinformatiker für Anwendungsentwicklung.
In dieser Episode geht es um häufige Fragen im Fachgespräch,
das Thema Vererbung. Viel Spaß!
Hallo, ich möchte heute meine Podcast-Reihe mit dem Thema
häufige Fragen im Fachgespräch fortsetzen.
Beim letzten Mal hatten wir uns über die Kapselung
im Zusammenhang mit der Objektorientierung unterhalten.
Heute möchte ich die zweite große Säule der Objektorientierung behandeln,
und zwar die Vererbung.
Ich habe die Episode wieder in drei Teile gegliedert.
Ich fange erst einmal an mit der Beantwortung der Frage,
so wie ich sie im Fachgespräch beantworten würde,
mit den verschiedensten Informationen, die man rund um die Vererbung so erzählen kann
in einer mündlichen Prüfung.
Dann würde ich ein wenig auf eine weiterführende Diskussion eingehen,
ein paar Beispiele bringen für Fragen, die man rund um die Vererbung
auch erwarten könnte in so einem Fachgespräch.
Und zuletzt gehe ich dann noch einmal auf die Entwicklerpraxis ein.
Wie könnte ich die Vererbung in der Praxis umsetzen?
Dann fangen wir doch mal an mit der Beantwortung der Frage im Fachgespräch.
Ich formuliere einfach jetzt mal ein paar Sätze,
so wie ich die Frage im Fachgespräch beantworten würde.
Wie gewohnt fange ich wieder mit einer abstrakten Definition an,
also mit einer ganz allgemeingültigen.
Ich versuche erstmal allgemein zu erklären, was denn Vererbung bedeutet,
und werde dann Schritt für Schritt weiter, tiefer in das Thema reingehen
und auf den Begriffen, die ich vielleicht schon definiert habe, dann aufbauen,
um somit einen schönen Rundum-Schlag zu machen
und das Thema Vererbung einmal komplett zu bearbeiten.
Also, ich fange mal an.
Klassen können Attribute und Methoden von anderen Klassen übernehmen,
indem sie von ihnen erben.
Bei dieser Vererbungsbeziehung haben wir einmal die vererbende Klasse,
die heißt Basisklasse, wird auch manchmal Eltern- oder Superklasse genannt,
und wir haben die erbende Klasse, das ist die Subklasse,
oder auch abgeleitete oder Kindklasse genannt.
Um von einer Klasse zu erben, schreibt man in Java Extents
und den Namen der Basisklasse hinter die Klassendefinition.
In C Sharp wird anstatt Extents der Doppelpunkt verwendet.
Als Beispiel in Java könnte man nehmen Class Auto Extents Fahrzeug.
Alle Attribute und Methoden der Basisklasse können jetzt in der Subklasse verwendet werden,
sofern sie dort sichtbar sind.
Das bedeutet, dass die Member in der Basisklasse entweder Public oder Protected sein müssen.
Public bedeutet, dass alle Klassen darauf zugreifen können,
Protected bedeutet, dass nur die definierende Klasse und alle abgeleiteten Klassen darauf zugreifen können.
Man kann eine Klasse mit dem Schlüsselwort Abstract als nicht-instanzierbar kennzeichnen.
Das bedeutet, dass man kein Objekt von dieser Klasse erzeugen kann
und dass Methoden und Attribute nur über Objekte der Subklassen dieser Klasse aufgerufen werden können.
Das benutzt man gerne im Kontext der Vererbung,
denn einige Klassen stellen einfach nur Basisfunktionalität bereit, ohne eine konkrete Instanz zu erlauben.
Diese Klassen würde ich dann Abstract machen, um dann mit ihren Subklassen konkret arbeiten zu können.
Man kann auch eine Methode als Abstract kennzeichnen.
Das bedeutet dann, dass sie in der Basisklasse nicht implementiert ist
und sie in den Subklassen implementiert werden muss.
Ich kann also das erzwingen, dass diese Implementierung in den Subklassen angeboten werden muss.
Sobald mindestens eine Methode einer Klasse Abstract ist, muss auch die gesamte Klasse Abstract sein.
Das ist auch logisch, denn wenn man eine Methode nicht implementiert,
das bedeutet ja, dass sie Abstract ist, kann man logischerweise auch kein Objekt dieser Klasse erzeugen,
denn eine Methode würde ja nicht funktionieren.
Nun ist es so, dass Methoden in Subklassen überschrieben werden können.
Das bedeutet, dass in der Subklasse eine Methode mit derselben Signatur wie in der Basisklasse implementiert wird.
Beim Aufruf einer Methode an einem Objekt dieser Subklasse wird jetzt diese neue,
diese überschriebene Methode verwendet und nicht die der Basisklasse.
Einige von mir sprachen, Java gehört dazu, erlauben standardmäßig das Überschreiben aller Methoden.
Während andere, wie zum Beispiel C-Sharp, es standardmäßig verbieten.
In Java kann man explizit Methoden mit dem Schlüsselwort FINAL als nicht überschreibbar kennzeichnen,
während man in C-Sharp genau das Gegenteil macht.
Dort kann man nämlich mit VIRTUAL eine Methode als überschreibbar kennzeichnen.
Auch ganze Klassen können als nicht überschreibbar gekennzeichnet werden.
Das bedeutet dann, dass man von denen eben nichts erben kann.
In Java wird das Schlüsselwort FINAL auch dafür benutzt.
Und in C-Sharp gibt es dafür ein anderes Schlüsselwort, das heißt SEALED.
Mit SUPER, dem Schlüsselwort, kann man innerhalb einer Subklasse auf Member der Basisklasse zugreifen.
Das braucht man zum Beispiel, wenn man den Konstruktor der Basisklasse aufrufen will.
Dann macht man SUPER, Klammer auf, Klammer zu, um den aufzurufen.
In den meisten modernen Programmiersprachen ist es heutzutage nur möglich, von einer einzigen Klasse zu erben.
Die sogenannte Mehrfachvererbung, wo es möglich wäre, eben von mehreren unterschiedlichen Klassen zu erben,
die ist dort verboten.
Prominentes Beispiel, wo sie erlaubt ist, ist C++.
Problem mit der Mehrfachvererbung ist das sogenannte Diamantenproblem.
Wenn eine Klasse eine Methode oder einen Member vererbt an zwei abgeleitete Klassen
und von diesen beiden Klassen gemeinsam erbt dann eine dritte Klasse,
dann kann das zu einem Problem führen, denn der Compiler kann nicht entscheiden,
welche konkrete Methode er zum Beispiel erben soll, von der einen oder von der anderen vererbenden Klasse.
In vielen objektorientierten Sprachen gibt es zu jeder Klasse mindestens eine Basisklasse.
In Java ist das zum Beispiel die Klasse Object.
Von dieser Klasse erben automatisch alle anderen Klassen,
egal ob selbst definiert oder von der Sprache selber bereitgestellt.
So, das wäre alles, was mir zu diesem Thema Vererbung eingefallen ist.
Ich habe das jetzt mal so vorstrukturiert, wie ich das erwarten würde,
vom Allgemeinen bis hin zum Speziellen, einige Schlüsselwörter eingestreut.
Die Unterscheidung zwischen Java und C Sharp und C++ und so weiter müsst ihr natürlich da nicht treffen.
Ich würde euch empfehlen, immer am Beispiel eurer eigenen Lieblingsprogrammiersprache
oder der Programmiersprache, mit der ihr im Betrieb arbeitet, die Sachen zu erklären.
Da steckt ihr am besten drin.
Da kommt ihr vielleicht auch auf Beispiele oder auf Probleme,
die ihr in eurer eigenen Praxis selber schon gehabt habt.
Und das ist meistens schöner in der Prüfung, weil man dort auch zeigen kann,
dass man wirklich damit gearbeitet hat und nicht einfach nur Lehrbuchantworten quasi auswendig gelernt hat.
Ich würde jetzt mal so ein paar Beispiele noch bringen,
was ich im Anschluss an so eine Diskussion vielleicht noch für Fragen stellen könnte.
Als allererstes möchte ich aber mit so einer kleinen Bitte beginnen.
Überlegt euch vor dem Fachgespräch, vor der möglichen Prüfung,
ein in irgendeiner Weise interessantes Beispiel für Vererbung.
Bitte nehmt nicht das klassische Autofahrzeugbeispiel,
das ganz, ganz viele Prüflinge immer in der Schule lehren.
Auf der einen Seite geht man auf Nummer sicher, wenn man das benutzt.
Auf der anderen Seite stellt man schnell fest,
dass das Beispiel in Details irgendwie an der einen oder anderen Stelle manchmal hakt.
Und wenn man da dann tiefer rein muss und das eigentlich auch noch nie in der Praxis umgesetzt hat,
weil das kaum jemand programmiert, so ein Beispiel, dann fängt man da ganz schnell an zu schwimmen.
Ich würde euch empfehlen, nehmt euch einfach ein Beispiel aus eurem eigenen Projekt.
Vielleicht habt ihr da ja eine Vererbung drin, die in irgendeiner Form sinnvoll ist, was ich hoffe.
Und die habt ihr tatsächlich selber umgesetzt und habt eure Erfahrungen damit gemacht
und wisst auch, warum ihr die Vererbung dort eingesetzt habt und was die Vor- und Nachteile vielleicht sind.
Das ist immer spannender und immer interessanter, als wenn ich zum 137. Mal das Beispiel mit dem Auto höre.
Also denkt euch da irgendwie ein nettes Beispiel aus und geht das aber auch im Kopf vor der Prüfung mal im Detail durch
und stellt dann nicht wehend der Prüfung fest, dass ihr doch eigentlich besser die Vererbung gar nicht benutzt haben solltet in eurem Projekt.
Was ich im Zuge der Vererbung, gerade auch beim Überschreiben von Methoden, dann immer gerne mal frage,
ist die Methodensignatur, was das denn wohl eigentlich ist.
Die Signatur einer Methode beinhaltet zum einen den Namen der Methode und die Art und Reihenfolge der Parameter.
Mit Art meine ich den Typ der Parameter.
Der Rückgabewert der Methode zählt nicht zur Signatur dazu.
Das kann man auch schön in Java sehen, wenn man eine Methode anlegt in einer Klasse,
in der es schon eine andere Methode gibt und gibt der den gleichen Namen und die gleichen Parameter aber einen anderen Rückgabewert,
dann wird Java sagen, tut mir leid, geht nicht, diese Methode gibt es bereits.
Das heißt, sie ist nicht unterschiedlich zu der, die es schon gibt.
Das bedeutet, dass die Methodensignatur eben übereinstimmt,
weil Java nur den Namen und die Parameter für die Signatur hernimmt und eben nicht den Rückgabewert.
Wichtig ist bei den Parametern, dass die Reihenfolge und der Typ relevant sind, nicht der Name.
Ich kann eben nicht eine Methode anlegen mit dem gleichen Namen und jeweils einem String-Parameter,
aber in der einen Methode nenne ich den String-Parameter Parameter 1 und in der anderen nenne ich ihn Parameter 2.
Dann wäre zwar der Name unterschiedlich, aber der Typ nicht,
aber der Name ist für Java völlig irrelevant und auch für andere Programmiesprachen übrigens.
Also es geht hier nur um den Typ der Parameter.
Im Prinzip sieht die Programmiersprache eine Signatur als Name, Klammer auf, String, Int, Char, Klammer zu.
Die Namen interessieren, also die Namen der Parameter, interessieren die Programmiersprache nicht.
Mit der Signatur, wenn man diesen Begriff beherrscht, kann man halt auch wunderbar eine weitere Frage beantworten,
und zwar die nach dem Unterschied zwischen dem Überladen und dem Überschreiben von Methoden.
Das Überladen ist etwas, was eigentlich nichts mit der Vererbung zu tun hat, wird aber halt gerne gefragt,
weil das Überschreiben eben erklärt werden müsste im Zusammenhang mit der Vererbung.
Und weil viele Prüflinge das gerne mal durcheinander bringen, würde ich da mal kurz drauf eingehen.
Also das Überschreiben von Methoden, das hatte ich eingangs schon erwähnt,
bedeutet, dass ich in einer Subklasse eine Methode mit derselben Signatur wie in der Basisklasse anlege
und damit diese Methode überschreibe. Das bedeutet, ich gebe ihr eine neue Implementierung.
Wenn jetzt mit dem Objekt der Subklasse gearbeitet wird und die Methode aufgerufen wird,
dann wird die Implementierung der Subklasse verwendet und nicht die der Basisklasse.
Das heißt, deren Funktionalität wurde verändert, die wurde überschrieben.
Beim Überladen von Methoden bin ich in einer Klasse, da geht es gar nicht um die Vererbung,
sondern dann definiere ich in einer Klasse eine zusätzliche Methode mit demselben Namen
wie dem einer anderen Methode in der Klasse, aber mit unterschiedlichen Parametern.
Hier gilt auch wieder, wie beim Überschreiben, mit unterschiedlichen Parametertypen.
Die Namen sind auch hier wieder relevant.
Beispiel dafür wäre aus Java die System.out.println-Methode.
Die ist ungefähr zehnmal überladen und zwar für jeden möglichen Datentyp, den man mit Println so ausgeben kann.
Und hier kommt auch genau der Sinn des Überladens ins Spiel.
Ich brauche den eigentlich nur in statisch typisierten Programmiersprachen,
wo ich einfach eine Funktionalität, der ich einen schönen Namen geben kann,
wie zum Beispiel Println, für unterschiedliche Datentypen anbieten möchte.
Und ich kann halt nicht in statisch typisierten Sprachen sowas machen wie Print und dann X.
Und das X kann ein Int oder ein String oder ein was auch immer sein.
Das geht nun mal nicht, denn der Compiler prüft die Typsicherheit beim Kompilieren.
Und wenn ich jetzt diese Funktionalität anbieten will, muss ich die halt für alle möglichen Datentypen,
für die die funktionieren soll, separat anbieten.
Ein schönes Beispiel dafür ist eine Addiere-Funktion.
Addiere int a, int b und addiere double a und double b.
Das wäre ein schönes Beispiel für das Überladen von Methoden.
Beim Überladen von Methoden kann übrigens auch der Rückgabewert der Methode unterschiedlich sein.
Das Beispiel von eben ist eigentlich ganz passend.
Wenn ich int a und int b addiere, sollte auch ein int c zurückkommen.
Bei double a und double b sollte logischerweise double c zurückkommen.
Eine kleine nette Frage, die mir auch ab und an noch mal einfällt,
ist, ob ich auch private Methoden überschreiben kann.
Dazu müsste ich jetzt als Prüfling erstmal sagen, prinzipiell nein,
denn private Methoden und private Member allgemein werden nicht an Subklassen vererbt.
Die sind dort nicht sichtbar und demnach auch nicht überschreibbar.
Dennoch kann ich tatsächlich in einer Subklasse eine Methode mit derselben Signatur
wie der einer Private Methode in der Basisklasse anlegen.
Das bedeutet aber nicht, dass ich die Funktionalität verändert habe,
sondern nur, dass ich die Methode der Basisklasse maskiert habe.
Oder im Englischen sagt man dann hidden, d.h. die Methode wurde versteckt von der in der Subklasse.
Eine weiterführende Diskussion der Vererbungen kann man dann z.B. mit Designpattern führen.
Designpattern verwenden sehr oft die Vererbungen.
Ein schönes Beispiel für etwas, was vielleicht der Anwender sogar in der Praxis schon eingesetzt hat,
auch wenn er vielleicht gar nicht weiß, dass es sich dabei um ein Designpattern handelt,
das ist die sogenannte Template Method, also die Vorlagen- oder Schablonen-Methode.
Und die verwendet zwei Konzepte aus der Vererbung und auch zwei Schlüsselwörter,
die man da gut dran erklären kann, und zwar einmal Final und einmal Abstract,
wenn wir jetzt mal bei Java bleiben.
Eine Vorlagen-Methode, also eine Template-Methode, die definiert einen Algorithmus
in Form einer feststehenden Reihenfolge von Teilschritten.
Ich nehme mal das Beispiel Kuchenbacken.
Beim Kuchenbacken muss ich erst die Zutaten in den Topf tun,
dann rühre ich die um und dann gieße ich die Zutaten auf das Backblech.
Ich kann das nicht in einer anderen Reihenfolge machen.
Ich kann nicht erst umrühren, dann aufs Backblech und dann die Zutaten rein.
Das funktioniert nicht.
Das heißt, ich backe einen Kuchen immer in genau dieser Reihenfolge.
Wenn das so ist, weiß ich, dass ich jetzt z.B. das Schlüsselwort Final benutzen könnte,
um diese Backe-Kuchen-Methode als nicht überschreibbar zu kennzeichnen.
Denn diese Reihenfolge ist nun mal fix vorgegeben und darf sich auch nicht verändern.
Und auch Subklassen dürfen die nicht einfach verändern, weil es dann zu Fehlern kommen würde.
Jetzt ist es aber so, dass ich vielleicht meinen Subklassen die konkrete Implementierung der Teilschritte
überlassen möchte, wie sie die umsetzen.
Als Beispiel, wie kriege ich meine Zutaten in den Topf?
Muss ich das Ei vorher aufknacken und dann da reinhauen?
Muss ich den Mehlbeutel aufmachen?
Muss ich den Zucker vorher irgendwie vorbereiten oder wie auch immer?
Das interessiert mich beim Backe-Kuchen eigentlich weniger.
Es geht nur darum, die Reihenfolge einzuhalten.
Wie ich z.B. die Zutaten konkret in den Topf packe, das ist der Subklass überlassen.
Das bedeutet, ich möchte der Subklasse erlauben, die Teilschritte zu überschreiben,
aber den Gesamtalgorithmus nicht.
Und wenn ich das möchte, dass die Teilschritte überschrieben werden können oder sogar müssen,
kann ich dafür Methoden definieren in der Basisklasse und diese als Abstract kennzeichnen.
Und genau das ist das Konzept der Template Method.
Ich lege den definierten Algorithmus fest in Form von abstrakten Methoden,
die in einer bestimmten Reihenfolge aufgerufen werden,
und die Methode, die diese Reihenfolge definiert, die kennzeichne ich als Final.
Und ich habe mal ein ganz konkretes Beispiel, was ich auch selber schon mal umgesetzt habe.
Schreiben einer XML-Datei.
Eine XML-Datei hat immer erstmal einen Kopf, dann kommen die Elemente und dann kommt der Fuß.
Es ist total unlogisch, erst den Fuß, dann den Kopf und dann die Elemente zu schreiben.
Das heißt, ich habe in einer Basisklasse definiert,
schreibe XML, macht erst, schreibe Kopf, dann schreibe Elemente und dann schreibe Fuß.
Aber wie die XML-Daten für die konkrete Subklasse jetzt aussehen,
ob ich jetzt eine XML-Datei mit Autos schreibe oder mit Büchern oder mit Schülern oder was auch immer,
das kann und will ich gar nicht definieren.
Deswegen gibt es in der Basisklasse nur diese drei abstrakten Methoden,
schreibe Kopf, schreibe Fuß und schreibe Elemente.
Und die Subklasse, die dann zum Beispiel Schüler-XML-Schreiber heißt,
die implementiert nur die abstrakten Methoden,
aber die Final-Schreibe-XML-Methode, die wird 1 zu 1 übernommen und darf auch nicht verändert werden.
Dann kommen wir zum letzten Kapitel für heute, und zwar den Tipps für die Entwicklerpraxis.
Ich persönlich bin der Meinung, dass die Vererbung eine ganz, ganz wichtige Rolle heutzutage spielt.
Viele objektorientierte Sprachen kennen die Vererbung und machen auch intensiv Gebrauch von ihr.
Und man kann auch als Entwickler natürlich von ihr profitieren.
Nicht umsonst ist es halt einer der wichtigsten Bereiche der Objektorientierung,
eine ganz wichtige Säule, sage ich mal, der Objektorientierung.
Also, da muss ich keine Angst vor haben.
Die sollte ich und kann ich auch gerne benutzen in der Programmierung.
Und man sieht auch an den Entwurfsmustern, ich habe einige Bücher zu den Entwurfsmustern in den Shownotes verlinkt,
so kann man sich den mal im Detail angucken, dass ganz viele davon auf Vererbung setzen.
Ich persönlich hätte aber ein Problem damit, die Vererbung zu stark oder zu schnell einzusetzen in der Programmierung.
Also viele Azubis zum Beispiel tendieren dazu zu sagen,
Vererbung kenne ich, das ist quasi zur Wiederverwendbarkeit super geeignet,
also lasse ich einfach mal alle möglichen Klassen von irgendwas erben,
nur damit ich eine Methode benutzen kann, die sie da definieren.
Und das ist nicht sinnvoll.
Denn das Vererben ist eine ganz, ganz starke Beziehung zwischen Klassen.
Wir hatten schon gehört, dass ich meistens nur von einer einzigen Klasse erben kann.
Und wenn ich mit der verdrahtet bin, sage ich mal, dann werde ich diese Verdrahtung sehr schwer wieder los.
Wenn ich jetzt von einer anderen Klasse doch erben möchte, dann habe ich auf einmal ein Problem.
Da muss ich wirklich die Klasse selber verändern.
Ich muss die Methoden, die ich vorher geerbt habe, auf einmal doch selber implementieren und hin und her.
Das ist also ein relativ großer Aufwand, das wieder loszuwerden.
Und deswegen würde ich dazu tendieren, die Vererbung nicht als Standardweg zu wählen,
sondern erstmal zu versuchen, das Ganze anders zu lösen.
Dafür gibt es auch einen schönen Merksatz, und zwar
Favor composition over inheritance.
Das bedeutet, ich sollte es immer bevorzugen, eine Komposition oder auch eine Delegierung an eine andere Klasse zu benutzen,
anstatt von einer Klasse zu erben.
Ganz wichtig bei der Vererbung, wenn ich das denn einsetze,
ist, an das Liskov Substitution Principle zu denken.
Das ist eines der 5 SOLID Principles,
und die sollte ich bei der Programmierung unbedingt einhalten.
Dabei ist es wichtig, dass die abgeleiteten Klassen
mindestens das Interface, also die Schnittstelle,
der Basisklasse bereitstellen und das in keiner Weise einschränken dürfen.
Wenn ich in meinen abgeleiteten Klassen Methoden in irgendeiner Form einschränke, die in der Basisklasse funktioniert haben,
dann widerspreche ich dem Liskov Substitution Principle.
Wir werden in der nächsten Folge, wenn es um die klassischen Fragen des Fachgesprächs geht, über die Polymorphie sprechen.
Und da werden wir das Thema sicherlich nochmal aufgreifen.
Um das Ganze so ein bisschen vorwegzunehmen, Vererbung ist ja eine Form der Polymorphie.
Und das bedeutet, dass ich alle Subklassen dort benutzen kann, wo ich auch die Basisklasse benutzen kann.
Wenn sich diese Subklassen dann aber nicht so verhalten wie die Basisklasse,
sondern Funktionen nicht bereitstellen oder anders implementieren,
dann ist eben dieses Prinzip der Ersetzbarkeit nicht mehr gewährleistet.
Und das darf nicht sein, weil dann der Code Annahmen trifft, die durch die Subklassen quasi nicht angeboten werden.
Und das kann dann in der Probierung nicht funktionieren.
Ein schönes Beispiel, was Onkel Bob in einem seiner Artikel mal gebracht hat, ist Quadrat und Rechteck.
Wenn man im Mathematikunterricht gut aufgepasst hat, dann weiß man, dass ein Quadrat eigentlich nur eine besondere Form eines Rechtecks ist.
Die grundsätzliche Form ist das Rechteck.
Die hat vier Seiten, von denen jeweils zwei gegenüberliegende die gleiche Länge haben.
Und alle Seiten sind mit rechten Winkeln verbunden.
Das definiert ein Rechteck.
Und jetzt kann ich sagen, dass ein Quadrat einfach eine besondere Form des Rechtecks ist.
Und zwar sind dort alle Seiten gleich lang.
Also beide gleich langen Seitenpaare haben zusätzlich noch die gleiche Länge.
Wenn ich dieses Hintergrundwissen habe, dann könnte ich jetzt vermuten, dass ich in meiner Programmierung das auch genauso umsetzen müsste.
Es gibt ja auch den Ausdruck, dass wenn eine Klasse von einer anderen erbt, dann eine Ist-Ein-Beziehung besteht.
Und das würde hier bestehen.
Ein Quadrat ist ein Rechteck.
Also bedeutet das, dass das Quadrat von dem Rechteck erben müsste eigentlich, ganz logisch.
Das mag in der Mathematik auch so sein und ist dort auch sinnvoll.
In der Programmierung führt das aber zu einem Problem.
Und wenn wir uns das mal im Detail angucken, dann wird das sehr schnell deutlich.
Wenn ich nämlich die Klasse Rechteck programmiere, dann muss ich eigentlich die beiden Seiten a und b individuell als Variablen anlegen.
Ich nenne die einfach mal a und b, angenommen es sind zwei Int-Variablen.
Wenn ich jetzt aber die Klasse Quadrat von Rechteck erben lasse, dann erbt sie ja diese beiden Attribute a und b.
Eigentlich dürfte das Quadrat aber nur eine einzige Seite haben.
Denn wenn wir a und b erben und auch korrekt setzen müssen, was wir tun müssen, denn wir sind ja ein Rechteck, dann kann das zu Problemen führen.
Ich muss nämlich auf einmal ganz seltsame Prüfungen machen.
Wenn nämlich die eine Seite gesetzt wird, muss automatisch die andere Seite mitgesetzt werden.
Stellen wir uns vor, es gibt zwei Setter, Set A und Set B.
Dann könnte ich mich natürlich fragen, was machen die denn jetzt eigentlich?
Warum gibt es überhaupt zwei Setter für zwei Seiten, wenn das Quadrat doch eigentlich nur einen haben dürfte?
Denn es hat ja überall die gleiche Länge.
Ich müsste also in Set A, wenn das aufgerufen wird, automatisch auch Set B aufrufen.
Und umgekehrt, wenn B gesetzt wird, auch wieder A aufrufen, damit die beiden Seiten identisch sind.
Und dann kann man sich vorstellen, wenn man das mal irgendwo vergisst, hat man auf einmal eine Inkonsistenz.
Also man sieht hier, wir haben hier Vererbung genutzt, um etwas darzustellen, was aus dem gesunden Menschenverstand heraus so dargestellt werden müsste,
aber in der Programmierung nicht so umgesetzt werden darf.
Weil wir hier einfach Dinge vererben, die in dieser Klasse Quadrat nicht vererbt werden dürfen, die dort keinen Platz haben.
Wenn wir nur eine Variable mit einem Wert haben dürfen, dann können und sollen wir das nicht mit zwei Werten umsetzen.
Das ist völliger Quatsch.
So, damit wäre ich für heute auch schon wieder am Ende.
Wir haben jetzt die zwei ersten Säulen der Objektorientierung durch, die Kapselung und die Vererbung.
Ich hatte es eben schon angesprochen, die Polymorphie, die fehlt noch und die wird in einer der nächsten Episoden dann nachgeholt.
Bis dahin möchte ich mich für das Zuhören bedanken.
Ich würde mich über Feedback zur Episode oder zum Podcast insgesamt freuen, über die Website oder über Mail at andungsentwicklerpodcast.de.
Ihr könnt mir auch gerne einen Kommentar hinterlassen oder ihr gebt mir ein Review bei iTunes, das würde mich auch sehr freuen.
Der Link zu dieser Episode ist wie gewohnt andungsentwicklerpodcast.de.
Dort findet ihr einige weiterführende Artikel zu den heute behandelten Themen und auch eine Literaturempfehlung, wo das Thema Vererbung nochmal im Detail erklärt wird.
Und damit möchte ich mich für heute verabschieden.
Ich hoffe, es hat euch gefallen.
Ich sage nochmal danke fürs Zuhören. Bis zum nächsten Mal und tschüss.
Untertitel der Amara.org-Community









Hallo Stefan,
danke für die gut aufbereitete Informationen.
Der Link zum Liskov Substitution Principle wird auf booked.net umgeleitet und enthällt nichts zum LSP.
Danke für den Hinweis. Habe ihn durch einen Wikipedia-Artikel ersetzt.