Visual C# 2012 Grundkurs

Mehrfachvererbung

Testen Sie unsere 1987 Kurse

10 Tage kostenlos!

Jetzt testen Alle Abonnements anzeigen
Die Ableitung einer Klasse von mehreren Klassen wird als Mehrfachvererbung bezeichnet. Obwohl C# dieses Konzept nicht unterstützt, verdeutlichen die Gründe dafür einige Grundkonzepte der objektorientierten Programmierung.

Transkript

In diesem Abschnitt möchte ich mich mit dem Konzept der Mehrfachvererbung beschäftigen. Unter Mehrfachvererbung versteht man die Ableitung einer Klasse von mehreren anderen Klassen. Um es gleich deutlich zu sagen: C# unterstützt die Mehrfachvererbung nicht. Aber: Bei der Betrachtung der Gründe, warum dem so ist, lernen wir einiges über die Konzepte der Objektorientierung, über Konzepte der Interfaces z.B., die in C# wiederum eine große Rolle spielen. Die Befürworter der Mehrfachvererbung sagen, dass immer, wenn man den Begriff "ist ein" verwendet, also z.B.: "Ein Auto ist ein Fahrzeug", dann liegt eine Vererbungsbeziehung vor. Und dieses Verhältnis zwischen Auto und Fahrzeug muss dann durch eine Vererbungslinie dargestellt werden. Im Gegensatz dazu steht dieses "hat ein" "Ein Auto hat vier Reifen." Diese Beziehung lässt sich also durch ein "hat" darstellen und ist dann eine Komposition oder eine Assoziation. In dem Sinne könnte man jetzt Situationen kreieren, wo eine Klasse in einem System mit einer "ist ein"-Beziehung von mehreren anderen Klassen im System abgeleitet werden könnte. Ich habe im Internet jetzt bei den Befürwortern der Mehrfachvererbung nach Beispielen gesucht, aber so richtig überzeugende Beispiele findet man tatsächlich nicht. Eines dieser Beispiele habe ich jetzt hier in einem Klassendiagramm skizziert. Ich habe also eine Basis-Klasse "Fahrzeug". Von dieser Basis-Klasse werden jetzt verschiedene Arten an Fahrzeugen abgeleitet, also jetzt erst mal nach der geologischen Benutzbarkeit zu Land, zu Wasser oder in der Luft und dann, auf der anderen Seite, nach ihren Antriebssystemen, also Elektrofahrzeug oder Fahrzeug mit Verbrennungsmotor. Jetzt könnte man aus dem Verhalten, das man diesen verschiedenen Typen an Fahrzeugen zuweist, konkrete Fahrzeuge zusammensetzen. Also, in dem Sinne wäre ein Elektroauto z.B. ein Elektrofahrzeug, das gleichzeitig ein Landfahrzeug wäre. Und ein Elektroboot wäre insofern dann ein Wasserfahrzeug mit Elektroantrieb und so weiter und so fort. Also, man könnte dann immer sagen, "Ein Auto ist ein Fahrzeug mit Verbrennungsmotor." Und: "Ein Auto ist ein Landfahrzeug." Es hätte also sowohl von der Logik her, als auch von der Implementierung nämlich dass ich nur einmal einen Code schreiben muss, der das Verhalten eines Fahrzeugs mit Verbrennungsmotor oder das Verhalten eines Fahrzeugs, das als Landfahrzeug dienen könnte, nur einmal implementieren und alle Ableitungen von diesen Landfahrzeugen und Verbrennungsmotorfahrzeugen könnten dann dieses Verhalten erben und benutzen. Insofern eigentlich eine ganz praktische Angelegenheit. Jetzt ist nur die Frage: Warum hat man sich bei C# dagegen entschieden, diese Möglichkeit zur Verfügung zu stellen? Um das zu zeigen, habe ich hier ein bisschen Code vorbereitet. Der Code ist sehr einfach. Ich habe hier eine generelle Basis-Klasse "A" und die definiert eine virtuelle Methode "Fu". Von "A" abgeleitet ist jetzt eine Klasse "B" und diese überschreibt die Methode "Fu". Also, sie hat ihre eigene spezifische Implementierung. Entsprechend habe ich hier eine Klasse "C" und die tut dasselbe. Diese hat also jetzt eine weitere spezifische Implementierung dieser Methode "Fu". So, und jetzt leite ich von den Klassen "B" und "C" eine Klasse "D" ab. Sie sehen schon, das Syntax Highlighting von C# bietet mir diese Option gar nicht an, hier "C" als weitere Klasse in diese Ableitungshierarchie einzutragen. Aber ich tue jetzt einfach mal so, als würde ich das wollen oder als würde das bei C# gehen. Ich leite also diese Klasse "D" von "B" und "C" ab. Und jetzt habe ich einen Kontext, in dem benutze ich ein Objekt der Klasse "D". Nun fängt das Problem an: Wenn ich hier die Methode "Fu" aufrufe, dann ist jetzt die Frage, welche der beiden Methoden "Fu" denn aufgerufen werden soll. Also, "D" hat selber keine Implementierung von "Fu", es erbt also die beiden Implementierungen von "B" und "C", aber man weiß jetzt eigentlich gar nicht: Welche soll es denn sein? Da müsste man jetzt eine zusätzliche komplexe Syntax einführen, damit man klar definieren kann, welche der Methoden es denn jetzt sein soll. Natürlich, man könnte jetzt sagen: Der Compiler gibt hier eine Warnung aus. Und man könnte dann etwa so was schreiben: "D.C.Fu". Also, ich hab hier das Objekt "D" und ich möchte aus dem Zweig "C" die Methode "Fu" verwenden. Entsprechend, wenn man die andere haben will, dann ein "B" hinschreiben. Sie sehen, es hätte schon Wege gegeben, es zu machen. Aber es ist nicht das einzige Problem, das dabei auftaucht. Schauen wir uns einmal das Speicherlayout an, das bei der Vererbung verwendet wird. Wenn ich eine Klasse "B "von der Klasse "A" ableite, dann ist das alles eigentlich gar kein Problem. Dieses Objekt "B" hat jetzt das Speicherlayout so ausgelegt, dass zunächst einmal der Speicherbedarf der Felder von der Klasse "A" abgelegt wird. Und danach folgt eben das, was die Klasse "B" an Speicher benötigt. Wenn ich jetzt ein Objekt vom Typ "B" in eine Variable von dem Typ "A" konvertiere, dann meint das System ja, dass es mit einem Objekt vom Typ "A" zu tun hat. Die Klasse "A" kennt ja nur diesen Speicherbereich. Und das ist aber alles auch ganz wunderbar. Also, ich konvertiere ein Objekt "B" in eine Variable von Typ "A" und damit wird dieser Speicherbereich benutzt und der Speicherbereich von "B", der wird völlig ignoriert. Entsprechend ist es, wenn ich ein Objekt vom Typ "C" anlege. Die Klasse "C" ist von der Klasse "A" abgeleitet, also kommt zuerst der Speicherbereich, den eben die Klasse "A" benötigt und dann kommt der Speicherbereich, den die Klasse "C" benötigt. Wenn ich jetzt so ein Objekt vom Typ "C" wiederum einer Variablen vom Typ "A" zuweise, dann wird eben vom ausführenden Code einfach nur dieser Speicherbereich benutzt, der von der Klasse "A" stammt und der Code weiß gar nicht, dass es da hinten dran noch irgendwie mehr gibt. Das interessiert ihn auch gar nicht. So, wie sieht aber das Speicherlayout bei einem Objekt der Klasse "D" aus? "D" ist ja jetzt gleichermaßen von "B" und "C" abgeleitet. Es muss also jetzt irgendwo von allen beteiligten Basis-Klassen einen Speicherbereich definieren und dann natürlich auch seine eigenen Felder noch unterbringen. Und jetzt haben wir hier so ein kleines Reihenfolgenproblem. Wenn ich ein Objekt der Klasse "D" einer Variablen vom Typ "B" zuweise, dann ist noch alles in Ordnung. Ein Objekt vom Typ "B", das erwartet ja jetzt einen Speicherbereich, der so aussieht. Das ist im Grunde genommen genau das. Und da das Speicherlayout von "D" ja auch genau so anfängt, ist es auch alles wunderbar. Aber sobald ich ein Objekt der Klasse "D" einer Variablen vom Typ "C" zuweise, habe ich jetzt ein Problem, weil das Speicherlayout vom "D" sieht ja so aus, dass zwischen dem was von "A" verwendet wird und von dem was von "C" verwendet wird, der Speicherbereich von "B" drinnen liegt. Es ist vom Compiler irgendwann einmal so festgelegt worden. Aber eine Variable vom Typ "C" erwartet ja ein Speicherlayout, das so aussieht. Also müsste praktisch der Code, der die Speicherbereiche von abgeleiteten Klassen anspricht, der müsste um etliches verkompliziert werden, um eine solche Situation überhaupt darstellen zu können. Die Schwierigkeiten, die dabei auftreten würden, sind es letztendlich nicht wert. Der Aufwand, den man betreiben müsste, auch der Zeitverlust, der durch diese ganzen Operationen entstehen würde, wenn man so etwas abbilden wollte, der ist letztendlich den Nutzen, den man aus Mehrfachvererbung ziehen kann, nicht wert. Jetzt gehen wir noch einmal zurück zu diesem Modell. Eigentlich ist es nämlich ein schlechtes Layout. Eigentlich würde ich oder jeder Software-Entwickler, der ein bisschen Erfahrung mit objektorientierten Systemen hat, ein System auch gar nicht so modellieren. Vielleicht kann man sagen, man leitet eine Klasse "Auto" von einer Klasse "Fahrzeug" ab, wenn das in irgendeiner Art und Weise Sinn macht. Das würde ich ja noch einsehen. Aber hier sieht es eigentlich so aus, dass ich diesen geologischen Aspekt eigentlich gar nicht in einer Vererbungshierarchie einbringe, ich sage, es handelt sich um einen Aspekt. Genau so wie hier der Aspekt des Antriebs. Nach meinem Dafürhalten wäre die richtige Beziehung, die ich hier darstellen muss, zu sagen: "Ein Fahrzeug hat einen Motor." Und nun habe ich eine Klasse "Motor" im System und kann Klassen "Elektromotor" und "Verbrennungsmotor" davon ableiten, etc. pp. mit dem entsprechenden Verhalten und kann dann sagen: OK, meine Klasse "Auto" hat einen Motor und dahinter verbirgt sich die ganze Antriebsphilosophie. Und meine Klasse "Fahrzeug" hat irgendwie einen geologischen Aspekt, hinter dem ich dann diese ganzen Eigenschaften der Bewegung auf dem Land, im Wasser, in der Luft etc. verbergen würde, etc. pp. Und aus all diesen Aspekten würde sich dann ein sehr flexibles System ergeben, in dem ich dann Implementierungen zusammenmischen kann. Ein einfacheres Beispiel der gleichen Situation zeigt sich hier: Ich hätte z.B. die Fähigkeit, ein Objekt zu klonen. Jedes Objekt, das diese Fähigkeit hat, könnte ja jetzt von einer Klasse "Clonable" abgeleitet sein. Entsprechend mit dem Aspekt der Persistenz, dass man also Objekte irgendwo speichern kann und dann wieder abrufen kann aus diesem gespeicherten Zustand. Wenn ich jetzt sage, ich hab hier eine Klasse "Produkt" und diese Klasse "Produkt" verfügt über die Eigenschaft, dass es von sich selbst einen Clone anlegen kann, also sich selbst kopieren kann, und diese Klasse soll die Fähigkeit haben, in einer Datenbank oder in einem sonstigen Speicher irgendwie abgelegt zu werden, dann könnte doch diese Klasse "Produkt" diese Fähigkeiten von Basis-Klassen erben. Aber auch hier, und gerade hier, sieht man, dass diese Logik der Mehrfachvererbung eigentlich völlig fehl am Platz ist. Was hier mehr am Platz ist, ist, dass man von Aspekten spricht, und diese Aspekte, die erbt man nicht, diese Aspekte, die implementiert man. Und dadurch landen wir bei einem ganz anderen Konzept, nämlich dem Konzept der Interfaces. OK, soweit zum Thema der Mehrfachvererbung. C# implementiert keine Mehrfachvererbung. Es gibt gute Gründe, warum C# keine Mehrfachvererbung unterstützt. Und wenn Sie in Ihren Projekten an einen Punkt kommen, wo Sie glauben, dass Sie Mehrfachvererbung bräuchten, dann kann ich Ihnen garantieren, dass Sie in Ihrem Modell möglicherweise eine unsaubere Betrachtung drin haben, die Sie vielleicht noch mal überdenken sollten.

Visual C# 2012 Grundkurs

Schreiben Sie eigene Programme in C# und lernen Sie dazu alle Schlüsselwörter und die meisten Konstrukte kennen, um sicher mit dieser Programmierspreche umzugehen.

7 Std. 1 min (44 Videos)
Derzeit sind keine Feedbacks vorhanden...
 

Dieser Online-Kurs ist als Download und als Streaming-Video verfügbar. Die gute Nachricht: Sie müssen sich nicht entscheiden - sobald Sie das Training erwerben, erhalten Sie Zugang zu beiden Optionen!

Der Download ermöglicht Ihnen die Offline-Nutzung des Trainings und bietet die Vorteile einer benutzerfreundlichen Abspielumgebung. Wenn Sie an verschiedenen Computern arbeiten, oder nicht den ganzen Kurs auf einmal herunterladen möchten, loggen Sie sich auf dieser Seite ein, um alle Videos des Trainings als Streaming-Video anzusehen.

Wir hoffen, dass Sie viel Freude und Erfolg mit diesem Video-Training haben werden. Falls Sie irgendwelche Fragen haben, zögern Sie nicht uns zu kontaktieren!