Die Idee, mehrere Vererbungen in einer Sprache zu unterstützen, hat mir immer gefallen. Meistens wird jedoch absichtlich darauf verzichtet, und der vermeintliche "Ersatz" sind Schnittstellen. Interfaces decken einfach nicht den gleichen Grund ab, den Mehrfachvererbung hat, und diese Einschränkung kann gelegentlich zu mehr Boilerplate-Code führen.
Der einzige Grund, den ich jemals dafür gehört habe, ist das Diamantproblem mit Basisklassen. Das kann ich einfach nicht akzeptieren. Für mich ist es eine Menge, wie: "Nun, es ist möglich , es zu vermasseln, also ist es automatisch eine schlechte Idee." Sie können alles in einer Programmiersprache vermasseln, und ich meine alles. Ich kann das einfach nicht ernst nehmen, zumindest nicht ohne eine gründlichere Erklärung.
Nur sich dieses Problems bewusst zu sein, ist 90% der Schlacht. Ich glaube, ich habe vor einigen Jahren von einem allgemeinen Workaround mit einem "Envelope" -Algorithmus oder ähnlichem gehört (läutet das eine Glocke?).
In Bezug auf das Diamantenproblem ist das einzige potenziell echte Problem, das mir einfällt, wenn Sie versuchen, eine Bibliothek eines Drittanbieters zu verwenden, und nicht sehen können, dass zwei scheinbar nicht miteinander verbundene Klassen in dieser Bibliothek eine gemeinsame Basisklasse haben, aber zusätzlich dazu Die Dokumentation, eine einfache Sprachfunktion, könnte beispielsweise erfordern, dass Sie ausdrücklich Ihre Absicht erklären, einen Diamanten zu erstellen, bevor er tatsächlich einen für Sie kompiliert. Mit einem solchen Merkmal ist jede Kreation eines Diamanten entweder beabsichtigt, rücksichtslos oder weil man sich dieser Fallstricke nicht bewusst ist.
Damit alles gesagt wird ... Gibt es einen wirklichen Grund, warum die meisten Menschen Mehrfachvererbung hassen, oder ist es nur ein Haufen Hysterie, der mehr Schaden als Nutzen verursacht? Gibt es etwas, das ich hier nicht sehe? Danke.
Beispiel
Auto erweitert WheeledVehicle, KIASpectra erweitert Auto und Electronic, KIASpectra enthält Radio. Warum enthält KIASpectra kein elektronisches Produkt?
Denn es ist ein Electronic. Vererbung vs. Komposition sollte immer eine Beziehung sein vs. eine Beziehung.
Denn es ist ein Electronic. Es gibt Drähte, Leiterplatten, Schalter usw., die das Ding auf und ab bewegen.
Denn es ist ein Electronic. Wenn Ihre Batterie im Winter leer ist, haben Sie genauso große Probleme, als wären plötzlich alle Räder verschwunden.
Warum keine Schnittstellen verwenden? Nehmen wir zum Beispiel # 3. Ich will nicht , um darüber zu schreiben und immer wieder, und ich wirklich nicht will , etwas bizarr Proxy Helfer Klasse erstellen , dies zu tun , entweder:
private void runOrDont()
{
if (this.battery)
{
if (this.battery.working && this.switchedOn)
{
this.run();
return;
}
}
this.dontRun();
}
(Wir werden uns nicht damit befassen, ob diese Implementierung gut oder schlecht ist.) Sie können sich vorstellen, wie viele dieser Funktionen in Zusammenhang mit Electronic stehen, die nichts mit WheeledVehicle zu tun haben, und umgekehrt.
Ich war mir nicht sicher, ob ich mich mit diesem Beispiel abfinden sollte oder nicht, da es dort Raum für Interpretationen gibt. Sie könnten auch in Bezug auf die Flugzeugerweiterung von Fahrzeug und FlyingObject und die Vogelerweiterung von Animal und FlyingObject denken, oder in Bezug auf ein viel reineres Beispiel.
Traits
- sie wirken wie Schnittstellen mit optionaler Implementierung, haben jedoch einige Einschränkungen, die das Auftreten von Problemen wie dem Diamantproblem verhindern.KiaSpectra
ist nicht einElectronic
; es hat Elektronik, und kann einElectronicCar
(was sich verlängern würdeCar
...)Antworten:
In vielen Fällen verwenden Menschen die Vererbung, um einer Klasse ein Merkmal zu verleihen. Denken Sie zum Beispiel an einen Pegasus. Bei mehrfacher Vererbung könnten Sie versucht sein zu sagen, dass der Pegasus Pferd und Vogel erweitert, weil Sie den Vogel als ein Tier mit Flügeln klassifiziert haben.
Vögel haben jedoch andere Eigenschaften, die Pegasi nicht hat. Zum Beispiel legen Vögel Eier, Pegasi haben Lebendgeburt. Wenn die Vererbung Ihr einziges Mittel ist, um gemeinsame Merkmale weiterzugeben, können Sie das Merkmal der Eiablage nicht vom Pegasus ausschließen.
Einige Sprachen haben sich dafür entschieden, Merkmale zu einem expliziten Konstrukt innerhalb der Sprache zu machen. Andere leiten Sie behutsam in diese Richtung, indem sie MI aus der Sprache entfernen. So oder so, ich kann mir keinen einzigen Fall vorstellen, in dem ich dachte "Mann, ich brauche wirklich MI, um das richtig zu machen".
Besprechen wir auch, was Vererbung WIRKLICH ist. Wenn Sie von einer Klasse erben, nehmen Sie eine Abhängigkeit von dieser Klasse an, müssen aber auch die Verträge unterstützen, die von dieser Klasse unterstützt werden, sowohl implizit als auch explizit.
Nehmen Sie das klassische Beispiel eines Quadrats, das von einem Rechteck erbt. Das Rechteck macht eine Eigenschaft für Länge und Breite sowie eine Methode getPerimeter und getArea verfügbar. Das Quadrat würde Länge und Breite überschreiben, so dass wenn eines festgelegt wird, das andere so festgelegt wird, dass es mit getPerimeter und getArea übereinstimmt (2 * Länge + 2 * Breite für Umfang und Länge * Breite für Fläche).
Es gibt einen einzelnen Testfall, der fehlschlägt, wenn Sie diese Implementierung eines Quadrats durch ein Rechteck ersetzen.
Es ist schwierig genug, mit einer einzigen Vererbungskette alles richtig zu machen. Es wird noch schlimmer, wenn Sie dem Mix einen weiteren hinzufügen.
Die Fallstricke, die ich im Zusammenhang mit dem Pegasus in MI und den Beziehungen Rechteck / Quadrat erwähnt habe, sind beide das Ergebnis eines unerfahrenen Entwurfs für Klassen. Das Vermeiden von Mehrfachvererbungen ist eine Möglichkeit, damit sich Entwickler nicht selbst in den Fuß schießen müssen. Wie bei allen Konstruktionsprinzipien können Sie durch die darauf basierende Disziplin und Schulung rechtzeitig erkennen, wann es in Ordnung ist, sich von ihnen zu lösen. Sehen Sie sich das Dreyfus-Modell für den Erwerb von Fähigkeiten an. Auf Expertenebene übersteigt Ihr intrinsisches Wissen die Abhängigkeit von Maximen / Prinzipien. Sie können fühlen, wenn eine Regel nicht zutrifft.
Und ich stimme zu, dass ich mit einem "realen" Beispiel, warum MI verpönt ist, etwas betrogen habe.
Schauen wir uns ein UI-Framework an. Lassen Sie uns speziell einige Widgets betrachten, die auf den ersten Blick so aussehen könnten, als wären sie einfach eine Kombination aus zwei anderen. Wie eine ComboBox. Eine ComboBox ist eine TextBox mit einer unterstützenden DropDownList. Dh ich kann einen Wert eingeben oder aus einer vorgegebenen Werteliste auswählen. Ein naiver Ansatz wäre, die ComboBox von TextBox und DropDownList zu erben.
Ihre Textbox leitet ihren Wert jedoch von dem ab, was der Benutzer eingegeben hat. Während die DDL ihren Wert von dem erhält, was der Benutzer auswählt. Wer hat einen Präzedenzfall? Die DDL wurde möglicherweise entwickelt, um Eingaben zu überprüfen und abzulehnen, die nicht in der ursprünglichen Werteliste enthalten waren. Überschreiben wir diese Logik? Das bedeutet, dass wir die interne Logik offen legen müssen, damit Erben überschreiben können. Oder, schlimmer noch, fügen Sie der Basisklasse Logik hinzu, die nur vorhanden ist, um eine Unterklasse zu unterstützen (was gegen das Abhängigkeitsinversionsprinzip verstößt ).
Wenn Sie MI vermeiden, können Sie diese Gefahr ganz umgehen. Dies kann dazu führen, dass Sie häufig wiederverwendbare Eigenschaften Ihrer UI-Widgets extrahieren, damit diese bei Bedarf angewendet werden können. Ein hervorragendes Beispiel hierfür ist die angehängte WPF-Eigenschaft , mit der ein Framework-Element in WPF eine Eigenschaft bereitstellen kann, die ein anderes Framework-Element verwenden kann, ohne vom übergeordneten Framework-Element zu erben.
Ein Raster ist beispielsweise ein Layoutbereich in WPF und verfügt über angehängte Spalten- und Zeileneigenschaften, die angeben, wo ein untergeordnetes Element in der Rasteranordnung platziert werden soll. Wenn ich ohne angehängte Eigenschaften eine Schaltfläche in einem Raster anordnen möchte, muss die Schaltfläche vom Raster abgeleitet werden, damit sie auf die Spalten- und Zeileneigenschaften zugreifen kann.
Die Entwickler haben dieses Konzept weiterentwickelt und angehängte Eigenschaften als Methode zum Komponieren des Verhaltens verwendet (hier ist beispielsweise mein Beitrag zum Erstellen einer sortierbaren GridView mit angehängten Eigenschaften, die geschrieben wurden, bevor WPF ein DataGrid enthielt). Der Ansatz wurde als XAML-Entwurfsmuster namens Attached Behaviors erkannt .
Hoffentlich lieferte dies ein wenig mehr Aufschluss darüber, warum Mehrfachvererbung in der Regel verpönt ist.
quelle
Durch das Ermöglichen der Mehrfachvererbung werden die Regeln für Funktionsüberladungen und den virtuellen Versand sowie die Implementierung von Sprachen für Objektlayouts deutlich schwieriger. Diese beeinflussen Sprachdesigner / -implementierer ziemlich stark und legen die ohnehin schon hohe Messlatte, um eine Sprache fertigzustellen, die stabil und adoptiert ist.
Ein weiteres häufiges Argument, das ich gesehen habe (und das ich manchmal gemacht habe), ist, dass Ihr Objekt mit zwei + Basisklassen fast immer gegen das Prinzip der einfachen Verantwortung verstößt. Entweder sind die Basisklassen + zwei nette, in sich geschlossene Klassen mit eigener Verantwortung (die die Verletzung verursacht), oder sie sind partielle / abstrakte Typen, die zusammenarbeiten, um eine einzige zusammenhängende Verantwortung zu bilden.
In diesem anderen Fall haben Sie 3 Szenarien:
Persönlich denke ich, dass Mehrfachvererbung einen schlechten Ruf hat und dass ein gut gemachtes System für die Komposition von Merkmalen wirklich mächtig / nützlich wäre ... aber es gibt viele Möglichkeiten, wie es schlecht implementiert werden kann, und viele Gründe dafür Keine gute Idee in einer Sprache wie C ++.
[edit] in Bezug auf dein Beispiel ist das absurd. Ein Kia hat Elektronik. Es hat einen Motor. Ebenso verfügt die Elektronik über eine Stromquelle, bei der es sich zufällig um eine Autobatterie handelt. Vererbung, geschweige denn Mehrfachvererbung hat dort keinen Platz.
quelle
Der einzige Grund, warum dies nicht erlaubt ist, besteht darin, dass es den Menschen leicht gemacht wird, sich in den Fuß zu schießen.
Was normalerweise in dieser Art von Diskussion folgt, sind Argumente, ob die Flexibilität der Werkzeuge wichtiger ist als die Sicherheit, nicht vom Fuß zu schießen. Auf dieses Argument gibt es keine eindeutig richtige Antwort, da die Antwort wie bei den meisten anderen Dingen in der Programmierung vom Kontext abhängt.
Wenn Ihre Entwickler mit MI vertraut sind und MI im Kontext Ihrer Arbeit Sinn macht, werden Sie es in einer Sprache, die es nicht unterstützt, sehr vermissen. Wenn sich das Team damit nicht wohl fühlt oder wenn es keinen wirklichen Bedarf gibt und die Leute es "nur weil sie können" verwenden, ist das kontraproduktiv.
Aber nein, es gibt kein überzeugendes, absolut wahres Argument, das die Mehrfachvererbung als schlechte Idee erweist.
BEARBEITEN
Die Antworten auf diese Frage scheinen einstimmig zu sein. Um der Anwalt des Teufels zu sein, werde ich ein gutes Beispiel für Mehrfachvererbung geben, bei der es nicht zu Hacks kommt.
Angenommen, Sie entwerfen eine Kapitalmarktanwendung. Sie benötigen ein Datenmodell für Wertpapiere. Einige Wertpapiere sind Aktienprodukte (Aktien, Immobilienfonds usw.), andere sind Schuldtitel (Anleihen, Unternehmensanleihen), andere sind Derivate (Optionen, Futures). Wenn Sie MI vermeiden, erstellen Sie einen sehr übersichtlichen, einfachen Vererbungsbaum. Eine Aktie erbt Eigenkapital, eine Anleihe Schulden. Bisher großartig, aber was ist mit Derivaten? Sie können auf Equity-ähnlichen Produkten oder Debit-ähnlichen Produkten basieren? Ok, ich denke, wir werden unseren Vererbungsbaumzweig weiter ausbauen. Denken Sie daran, dass einige Derivate auf Aktienprodukten, Schuldtiteln oder beiden basieren. Unser Erbschaftsbaum wird also immer komplizierter. Dann kommt der Business Analyst und teilt Ihnen mit, dass wir jetzt indexierte Wertpapiere (Indexoptionen, Index-Future-Optionen) unterstützen. Und diese Dinge können auf Eigenkapital, Schulden oder Derivaten basieren. Das wird unordentlich! Leiten meine zukünftigen Indexoptionen Aktien-> Aktien-> Optionen-> Index ab? Warum nicht Aktien-> Aktien-> Index-> Optionen? Was ist, wenn ich eines Tages beide in meinem Code finde?
Das Problem hierbei ist, dass diese Grundtypen in jeder Permutation gemischt werden können, die sich natürlich nicht voneinander ableitet. Die Objekte sind durch eine Beziehung definiert, so dass Komposition überhaupt keinen Sinn ergibt. Mehrfachvererbung (oder das ähnliche Konzept von Mixins) ist hier die einzige logische Darstellung.
Die eigentliche Lösung für dieses Problem besteht darin, die Datentypen Equity, Debt, Derivative, Index unter Verwendung von Mehrfachvererbung zu definieren und zu mischen, um Ihr Datenmodell zu erstellen. Dadurch werden Objekte erstellt, die beide sinnvoll sind und sich leicht zur Wiederverwendung von Code eignen.
quelle
Equity
undDebt
beide implementierenISecurity
.Derivative
hat eineISecurity
Eigenschaft. Es kann sich um eineISecurity
gegebenenfalls (ich weiß nicht, Finanzen).IndexedSecurities
Enthält wieder eine Interface-Eigenschaft, die auf die Typen angewendet wird, auf denen sie basieren dürfen. Wenn sie alle sindISecurity
, dann haben sie alleISecurity
Eigenschaften und können beliebig verschachtelt werden ...Die anderen Antworten scheinen sich hauptsächlich mit der Theorie zu befassen. Hier ist ein konkretes Python-Beispiel, das vereinfacht dargestellt ist und in das ich kopfüber hineingebrochen bin.
Bar
wurde unter der Annahme geschrieben, dass es eine eigene Implementierung von hatzeta()
, was im Allgemeinen eine ziemlich gute Annahme ist. Eine Unterklasse sollte sie entsprechend überschreiben, damit sie das Richtige tut. Leider waren die Namen nur zufällig gleich - sie taten etwas anderes,Bar
riefen aber jetzt dieFoo
Umsetzung auf:Es ist ziemlich frustrierend, wenn keine Fehler ausgegeben werden, die Anwendung sich nur leicht falsch
Bar.zeta
verhält und die Codeänderung, die sie verursacht hat, nicht das Problem zu sein scheint.quelle
super()
?super()
die sichBang
inBar, Foo
würde das Problem ebenfalls beheben - aber Ihr Punkt ist gut, +1.Bar.zeta(self)
.Ich würde argumentieren, dass es mit MI in der richtigen Sprache keine wirklichen Probleme gibt . Der Schlüssel ist, Diamantstrukturen zuzulassen, aber zu verlangen, dass Subtypen ihre eigene Außerkraftsetzung bereitstellen, anstatt dass der Compiler eine der Implementierungen basierend auf einer Regel auswählt.
Ich mache das in Guava , einer Sprache, an der ich arbeite. Ein Merkmal von Guava ist, dass wir die Implementierung einer Methode durch einen bestimmten Supertyp aufrufen können. So ist es einfach, ohne spezielle Syntax anzugeben, welche Supertype-Implementierung "geerbt" werden soll:
Wenn wir kein
OrderedSet
eigenestoString
angeben, erhalten wir einen Kompilierungsfehler. Keine Überraschungen.Ich finde MI besonders nützlich für Sammlungen. Ich verwende beispielsweise gerne einen
RandomlyEnumerableSequence
Typ, um zu vermeiden,getEnumerator
dass Arrays, Deques usw. deklariert werden :Wenn wir kein MI hätten, könnten wir ein
RandomAccessEnumerator
für mehrere Sammlungen schreiben, aber wenn wir eine kurzegetEnumerator
Methode schreiben müssten, fügt das immer noch Boilerplate hinzu.In ähnlicher Weise ist MI nützlich für die Vererbungsstandardimplementierungen von
equals
,hashCode
undtoString
für Sammlungen.quelle
toString
das Verhalten des Benutzers, sodass das Überschreiben des Verhaltens nicht gegen das Substitutionsprinzip verstößt. Manchmal kommt es auch zu "Konflikten" zwischen Methoden, die dasselbe Verhalten, jedoch unterschiedliche Algorithmen aufweisen, insbesondere bei Auflistungen.Ordered extends Set and Sequence
aDiamond
? Es ist nur ein Join. Es fehlt derhat
Scheitelpunkt. Warum nennst du es einen Diamanten? Ich habe hier gefragt , aber es scheint eine Tabu-Frage. Woher wussten Sie, dass Sie diese dreieckige Struktur eher als Diamant als als Verbinden bezeichnen müssen?Top
Typ, der deklarierttoString
, also gab es tatsächlich eine Diamantstruktur. Aber ich denke, Sie haben einen Punkt - ein "Join" ohne eine Raute-Struktur erzeugt ein ähnliches Problem, und die meisten Sprachen behandeln beide Fälle auf die gleiche Weise.Vererbung, mehrfach oder auf andere Weise, ist nicht so wichtig. Wenn zwei Objekte unterschiedlichen Typs substituierbar sind, ist dies wichtig, auch wenn sie nicht durch Vererbung verbunden sind.
Eine verknüpfte Liste und eine Zeichenfolge haben wenig gemeinsam und müssen nicht durch Vererbung verknüpft werden. Es ist jedoch nützlich, wenn ich mithilfe einer
length
Funktion die Anzahl der Elemente in einem der beiden Elemente ermitteln kann.Vererbung ist ein Trick, um die wiederholte Implementierung von Code zu vermeiden. Wenn die Vererbung Ihre Arbeit spart und die Mehrfachvererbung im Vergleich zur Einzelvererbung noch mehr Arbeit spart, ist dies die einzige Rechtfertigung, die erforderlich ist.
Ich vermute, dass einige Sprachen Mehrfachvererbung nicht sehr gut implementieren, und für die Praktiker dieser Sprachen bedeutet Mehrfachvererbung dies. Erwähnen Sie die Mehrfachvererbung für einen C ++ - Programmierer, und was Ihnen einfällt, sind Probleme, wenn eine Klasse über zwei verschiedene Vererbungspfade zwei Kopien einer Basis erhält und ob sie
virtual
für eine Basisklasse verwendet werden soll, und Unklarheiten darüber, wie Destruktoren aufgerufen werden , und so weiter.In vielen Sprachen ist die Vererbung von Klassen mit der Vererbung von Symbolen verbunden. Wenn Sie eine Klasse D von einer Klasse B ableiten, erstellen Sie nicht nur eine Typbeziehung. Da diese Klassen auch als lexikalische Namespaces dienen, müssen Sie zusätzlich auch Symbole aus dem B-Namespace in den D-Namespace importieren die Semantik dessen, was mit den Typen B und D selbst geschieht. Die Mehrfachvererbung führt daher zu Problemen mit Symbolkonflikten. Wenn wir von
card_deck
und erbengraphic
, die beide einedraw
Methode "haben" , was bedeutet das fürdraw
das resultierende Objekt? Ein Objektsystem, das dieses Problem nicht hat, ist das in Common Lisp. Vielleicht nicht zufällig wird Mehrfachvererbung in Lisp-Programmen verwendet.Schlecht umgesetzt, unbequem etwas (wie Mehrfachvererbung) sollten gehasst werden.
quelle
length
Operation ersetzen zu können, ist unabhängig vom Konzept der Vererbung nützlich (was in diesem Fall nicht hilft: Es ist unwahrscheinlich, dass wir irgendetwas erreichen können, wenn wir versuchen, die Implementierung zwischen beiden zu teilen die beiden Methoden derlength
Funktion). Es könnte dennoch zu einer abstrakten Vererbung kommen: z. B. sind sowohl Liste als auch Zeichenfolge von der Typsequenz (die jedoch keine Implementierung bietet).Soweit ich das beurteilen kann, ist ein Teil des Problems (abgesehen davon, dass Ihr Design ein bisschen schwieriger zu verstehen ist (und dennoch einfacher zu codieren ist)), dass der Compiler genug Platz für Ihre Klassendaten spart, was eine enorme Menge von zulässt Speicherverschwendung in folgendem Fall:
(Mein Beispiel ist vielleicht nicht das beste, aber ich versuche, das Wesentliche über mehrere Speicherbereiche zum selben Zweck herauszufinden. Es war das erste, woran ich dachte: P)
Betrachtet man eine DDD, bei der der Klassenhund von caninus und pet ausgeht, hat ein caninus eine Variable, die die Menge an Futter angibt, die er essen soll (eine ganze Zahl), unter dem Namen dietKg, aber ein Pet hat für diesen Zweck gewöhnlich auch eine andere Variable unter derselben name (es sei denn, Sie setzen einen anderen Variablennamen, dann müssen Sie zusätzlichen Code codieren, was das ursprüngliche Problem war, das Sie vermeiden wollten, um die Integrität der Variablen zu behandeln und beizubehalten). Dann haben Sie genau zwei Speicherplätze Um dies zu vermeiden, müssen Sie Ihren Compiler ändern, um diesen Namen unter demselben Namespace zu erkennen, und diesen Daten nur einen einzigen Speicherbereich zuweisen, der leider in der Kompilierungszeit nicht eindeutig festgelegt werden kann.
Sie können natürlich eine Sprache entwerfen, um anzugeben, dass für eine solche Variable möglicherweise bereits ein Speicherplatz an einer anderen Stelle definiert ist. Am Ende sollte der Programmierer jedoch angeben, auf welchen Speicherplatz sich diese Variable bezieht (und erneut zusätzlichen Code).
Vertrauen Sie mir, die Leute, die diesen Gedanken wirklich hart umsetzen, aber ich bin froh, dass Sie gefragt haben, ob Ihre Art derjenige ist, der die Paradigmen ändert;), und ich sage nicht, dass dies unmöglich ist (aber viele Annahmen und Ein mehrphasiger Compiler muss implementiert werden, und ein wirklich komplexer. Ich sage nur, dass er noch nicht existiert. Wenn Sie ein Projekt für Ihren eigenen Compiler starten, der dazu in der Lage ist (Mehrfachvererbung), lassen Sie es mich bitte wissen Ich freue mich, mich Ihrem Team anzuschließen.
quelle
Mir ist schon lange nicht mehr so recht aufgefallen, wie sehr sich einige Programmieraufgaben von anderen unterscheiden - und wie hilfreich es ist, wenn die verwendeten Sprachen und Muster auf den Problemraum zugeschnitten sind.
Wenn Sie alleine oder größtenteils isoliert an Code arbeiten, den Sie geschrieben haben, ist dies ein völlig anderer Problembereich als das Erben einer Codebasis von 40 Personen in Indien, die ein Jahr lang daran gearbeitet haben, bevor sie es Ihnen ohne Übergangshilfe ausgehändigt haben.
Stellen Sie sich vor, Sie wurden gerade von Ihrer Traumfirma eingestellt und haben dann eine solche Codebasis geerbt. Stellen Sie sich außerdem vor, die Berater hätten etwas über Vererbung und Mehrfachvererbung gelernt (und waren daher von dieser fasziniert) ... Stellen Sie sich vor, woran Sie möglicherweise arbeiten.
Wenn Sie Code erben, ist das wichtigste Merkmal, dass er verständlich ist und die Teile isoliert sind, sodass sie unabhängig voneinander bearbeitet werden können. Sicher, wenn Sie die Code-Strukturen zum ersten Mal schreiben, wie Mehrfachvererbung, ersparen Sie sich möglicherweise ein wenig Doppelarbeit und passen zu Ihrer damaligen logischen Stimmung, aber der nächste Typ hat einfach mehr zu entwirren.
Jede Verknüpfung in Ihrem Code erschwert es auch, Teile unabhängig voneinander zu verstehen und zu ändern, und zwar doppelt, wenn mehrere Elemente vererbt werden.
Wenn Sie als Teil eines Teams arbeiten, möchten Sie auf den einfachsten Code abzielen, der Ihnen absolut keine redundante Logik bietet. (Dies bedeutet DRY, nicht, dass Sie nicht viel eingeben sollten, nur, dass Sie Ihren Code nie in 2 ändern müssen Orte, um ein Problem zu lösen!)
Es gibt einfachere Möglichkeiten, DRY-Code zu erstellen als Multiple Inheritance. Wenn Sie ihn also in eine Sprache aufnehmen, können Sie nur auf Probleme stoßen, die von anderen Personen verursacht wurden, die möglicherweise nicht den gleichen Kenntnisstand wie Sie haben. Es ist nur verlockend, wenn Ihre Sprache nicht in der Lage ist, Ihnen eine einfache / weniger komplexe Möglichkeit zu bieten, Ihren Code trocken zu halten.
quelle
Das Hauptargument gegen Mehrfachvererbung ist, dass einige nützliche Fähigkeiten bereitgestellt werden können und einige nützliche Axiome in einem Rahmen gelten können, der sie stark einschränkt (*), aber nicht ohne solche Einschränkungen bereitgestellt und / oder gehalten werden kann. Unter ihnen:
Die Möglichkeit, mehrere separat kompilierte Module zu haben, umfasst Klassen, die von den Klassen anderer Module erben, und die Neukompilierung eines Moduls, das eine Basisklasse enthält, ohne dass jedes Modul neu kompiliert werden muss, das von dieser Basisklasse erbt.
Die Fähigkeit eines Typs, eine Member-Implementierung von einer übergeordneten Klasse zu erben, ohne dass der abgeleitete Typ sie erneut implementieren muss
Das Axiom, dass jede Objektinstanz direkt auf sich selbst oder einen ihrer Basistypen aufwärts oder abwärts gerichtet sein kann, und solche Aufwärts- und Abwärtsbewegungen in beliebiger Kombination oder Reihenfolge sind immer identitätserhaltend
Das Axiom, dass, wenn eine abgeleitete Klasse ein Basisklassenmitglied überschreibt und verkettet, das Basismitglied direkt durch den Verkettungscode aufgerufen wird und nirgendwo anders aufgerufen wird.
(*) In der Regel müssen Typen, die Mehrfachvererbung unterstützen, als "Interfaces" deklariert werden, anstatt als Klassen, und Interfaces dürfen nicht alles tun, was normale Klassen können.
Wenn man eine verallgemeinerte Mehrfachvererbung zulassen will, muss etwas anderes nachgeben. Wenn X und Y beide von B erben, überschreiben sie dasselbe Element M und verketten es mit der Basisimplementierung. Wenn D von X und Y erbt, aber M nicht überschreibt, dann sollte (( B) q) .M () tun? Das Ablehnen einer solchen Besetzung verstößt gegen das Axiom, wonach jedes Objekt auf jeden Basistyp hochgestuft werden kann, aber jedes mögliche Verhalten der Besetzung und des Aufrufs von Mitgliedern verstößt gegen das Axiom der Methodenverkettung. Es kann erforderlich sein, dass Klassen nur in Kombination mit der jeweiligen Version einer Basisklasse geladen werden, für die sie kompiliert wurden. Dies ist jedoch häufig umständlich. Wenn sich die Laufzeitumgebung weigert, einen beliebigen Typ zu laden, in dem ein beliebiger Vorfahr auf mehr als einer Route erreichbar ist, ist dies möglicherweise möglich. würde aber den Nutzen der Mehrfachvererbung stark einschränken. Das Zulassen gemeinsamer Vererbungspfade nur dann, wenn keine Konflikte vorliegen, würde Situationen schaffen, in denen eine alte Version von X mit einem alten oder neuen Y kompatibel wäre und ein altes Y mit einem alten oder neuen X kompatibel wäre, das neue X und das neue Y jedoch kompatibel sein, auch wenn nichts getan hat, von dem an und für sich eine bahnbrechende Veränderung erwartet werden sollte.
Einige Sprachen und Frameworks erlauben eine Mehrfachvererbung. Die Theorie besagt, dass das, was aus MI gewonnen wird, wichtiger ist als das, was aufgegeben werden muss, um dies zu ermöglichen. Die Kosten für MI sind jedoch erheblich, und in vielen Fällen bieten Schnittstellen 90% der Vorteile von MI zu einem geringen Teil der Kosten.
quelle
FirstDerivedFoo
"override of"Bar
nichts anderes tut als "chain to a protected non-virtual"FirstDerivedFoo_Bar
undSecondDerivedFoo
"override chains to protected non-virtual"SecondDerivedBar
, und wenn Code, der auf die Basismethode zugreifen möchte, diese geschützten nicht virtuellen Methoden verwendet, dann das könnte ein praktikabler Ansatz sein ...base.VirtualMethod
), aber ich kenne keine Sprachunterstützung, um einen solchen Ansatz besonders zu erleichtern. Ich wünschte, es gäbe eine saubere Möglichkeit, einen Codeblock sowohl an eine nicht virtuelle geschützte Methode als auch an eine virtuelle Methode mit derselben Signatur anzuhängen, ohne dass eine virtuelle Einzeilenmethode erforderlich wäre (die viel mehr als eine Zeile beansprucht) im Quellcode).