Warum sollte ich Mehrfachvererbung in C ++ vermeiden?


167

Ist es ein gutes Konzept, Mehrfachvererbung zu verwenden, oder kann ich stattdessen andere Dinge tun?

Antworten:


259

Mehrfachvererbung (abgekürzt als MI) riecht , was bedeutet, dass dies normalerweise aus schlechten Gründen durchgeführt wurde und dem Betreuer ins Gesicht bläst.

Zusammenfassung

  1. Betrachten Sie die Zusammensetzung von Features anstelle der Vererbung
  2. Seien Sie vorsichtig mit dem Diamond of Dread
  3. Betrachten Sie die Vererbung mehrerer Schnittstellen anstelle von Objekten
  4. Manchmal ist Mehrfachvererbung das Richtige. Wenn ja, dann benutze es.
  5. Seien Sie bereit, Ihre mehrfach vererbte Architektur in Codeüberprüfungen zu verteidigen

1. Vielleicht Komposition?

Dies gilt für die Vererbung und umso mehr für die Mehrfachvererbung.

Muss Ihr Objekt wirklich von einem anderen erben? A Carmuss nicht von a erben, um Enginezu arbeiten, noch von a Wheel. A Carhat eine Engineund vier Wheel.

Wenn Sie die Mehrfachvererbung verwenden, um diese Probleme anstelle der Komposition zu lösen, haben Sie etwas falsch gemacht.

2. Der Diamant der Angst

Normalerweise müssen Sie eine Klasse A, dann Bund Cbeide vererben A. Und (frag mich nicht warum) jemand entscheidet dann, dass er Dsowohl von Bals auch erben muss C.

Ich bin in 8 acht Jahren zweimal auf diese Art von Problem gestoßen, und es ist amüsant zu sehen, weil:

  1. Wie viel von einem Fehler war es von Anfang an (in beiden Fällen Dsollte nicht von beiden geerbt werden Bund C), weil dies eine schlechte Architektur war (in der Tat Csollte überhaupt nicht existieren ...)
  2. Wie viel Betreuer haben dafür bezahlt, weil in C ++ die übergeordnete Klasse Ain ihrer Enkelklasse zweimal vorhanden Dwar. Daher A::fieldbedeutete das Aktualisieren eines übergeordneten Felds, dass es entweder zweimal (durch B::fieldund C::field) aktualisiert wurde oder dass später etwas lautlos schief ging und abstürzte (Zeiger neu einfügen B::fieldund löschen C::field...)

Wenn Sie das Schlüsselwort virtual in C ++ verwenden, um die Vererbung zu qualifizieren, wird das oben beschriebene doppelte Layout vermieden, wenn dies nicht das ist, was Sie wollen, aber meiner Erfahrung nach machen Sie wahrscheinlich etwas falsch ...

In der Objekthierarchie sollten Sie versuchen, die Hierarchie als Baum (ein Knoten hat EIN übergeordnetes Element) und nicht als Diagramm beizubehalten.

Mehr über den Diamanten (bearbeiten 2017-05-03)

Das eigentliche Problem mit dem Diamond of Dread in C ++ ( vorausgesetzt, das Design ist solide - lassen Sie Ihren Code überprüfen! ) Ist , dass Sie eine Auswahl treffen müssen :

  • Ist es wünschenswert, dass die Klasse Azweimal in Ihrem Layout vorhanden ist, und was bedeutet das? Wenn ja, dann erben Sie auf jeden Fall zweimal davon.
  • sollte es nur einmal existieren, dann erben Sie virtuell davon.

Diese Wahl ist dem Problem inhärent, und in C ++ können Sie dies im Gegensatz zu anderen Sprachen tatsächlich tun, ohne dass ein Dogma Ihr Design auf Sprachebene erzwingt.

Aber wie bei allen Kräften geht auch bei dieser Macht die Verantwortung einher: Lassen Sie Ihr Design überprüfen.

3. Schnittstellen

Die Mehrfachvererbung von null oder einer konkreten Klasse und null oder mehr Schnittstellen ist normalerweise in Ordnung, da Sie den oben beschriebenen Diamond of Dread nicht antreffen. In der Tat wird dies in Java so gemacht.

Normalerweise meinen Sie, wenn C von Aund erbt, Bdass Benutzer verwenden können, Cals ob es ein Aund / oder als ob es ein wäre B.

In C ++ ist eine Schnittstelle eine abstrakte Klasse mit:

  1. Alle Methoden wurden als rein virtuell deklariert (mit dem Suffix = 0) (entfernt vom 03.05.2017).
  2. Keine Mitgliedsvariablen

Die Mehrfachvererbung von null zu einem realen Objekt und null oder mehr Schnittstellen wird nicht als "stinkend" angesehen (zumindest nicht so viel).

Mehr über die C ++ Abstract Interface (bearbeiten 2017-05-03)

Erstens kann das NVI-Muster verwendet werden, um eine Schnittstelle zu erzeugen, da das eigentliche Kriterium darin besteht, keinen Status zu haben (dh keine Mitgliedsvariablen, außer this). Der Sinn Ihrer abstrakten Schnittstelle besteht darin, einen Vertrag zu veröffentlichen ("Sie können mich so und so nennen"), nicht mehr und nicht weniger. Die Einschränkung, nur eine abstrakte virtuelle Methode zu haben, sollte eine Entwurfsentscheidung sein, keine Verpflichtung.

Zweitens ist es in C ++ sinnvoll, virtuell von abstrakten Schnittstellen zu erben (auch mit den zusätzlichen Kosten / Indirektion). Wenn Sie dies nicht tun und die Schnittstellenvererbung in Ihrer Hierarchie mehrmals angezeigt wird, treten Unklarheiten auf.

Drittens ist die Objektorientierung großartig, aber es ist nicht die einzige Wahrheit da draußen TM in C ++. Verwenden Sie die richtigen Tools und denken Sie immer daran, dass Sie in C ++ andere Paradigmen haben, die andere Arten von Lösungen anbieten.

4. Benötigen Sie wirklich Mehrfachvererbung?

Manchmal ja.

Normalerweise Cerbt Ihre Klasse von Aund Bund Aund Bsind zwei nicht verwandte Objekte (dh nicht in derselben Hierarchie, nichts gemeinsam, unterschiedliche Konzepte usw.).

Sie könnten beispielsweise ein System Nodesmit X-, Y- und Z-Koordinaten haben, das viele geometrische Berechnungen durchführen kann (möglicherweise einen Punkt, einen Teil geometrischer Objekte), und jeder Knoten ist ein automatisierter Agent, der mit anderen Agenten kommunizieren kann.

Vielleicht haben Sie bereits Zugriff auf zwei Bibliotheken mit jeweils einem eigenen Namespace (ein weiterer Grund für die Verwendung von Namespaces ... Aber Sie verwenden Namespaces, nicht wahr?), Ein Wesen geound das andere Wesenai

Sie haben also Ihre eigenen own::NodeAbleitungen von ai::Agentund geo::Point.

Dies ist der Moment, in dem Sie sich fragen sollten, ob Sie stattdessen keine Komposition verwenden sollten. Wenn own::Nodees wirklich sowohl a ai::Agentals auch a ist geo::Point, reicht die Komposition nicht aus.

Dann benötigen Sie mehrere Vererbungen, damit Sie own::Nodemit anderen Agenten entsprechend ihrer Position in einem 3D-Raum kommunizieren können.

(Sie werden feststellen, dass ai::Agentund geo::Pointvollständig, vollständig, vollständig unabhängig sind ... Dies verringert die Gefahr einer Mehrfachvererbung drastisch.)

Andere Fälle (bearbeiten 2017-05-03)

Es gibt andere Fälle:

  • Verwenden der (hoffentlich privaten) Vererbung als Implementierungsdetail
  • Einige C ++ - Redewendungen wie Richtlinien können eine Mehrfachvererbung verwenden (wenn jeder Teil über die anderen mit kommunizieren muss this).
  • die virtuelle Vererbung von std :: exception ( Ist die virtuelle Vererbung für Ausnahmen erforderlich? )
  • etc.

Manchmal kann man Komposition verwenden, und manchmal ist MI besser. Der Punkt ist: Sie haben die Wahl. Tun Sie es verantwortungsbewusst (und lassen Sie Ihren Code überprüfen).

5. Soll ich also Mehrfachvererbung durchführen?

Nach meiner Erfahrung die meiste Zeit nein. MI ist nicht das richtige Werkzeug, auch wenn es zu funktionieren scheint, da es von den Faulen verwendet werden kann, um Features zu stapeln, ohne die Konsequenzen zu erkennen (wie das Erstellen eines Carsowohl als auch Engineeines Wheel).

Aber manchmal ja. Und zu diesem Zeitpunkt wird nichts besser funktionieren als MI.

Aber weil MI stinkt, sollten Sie darauf vorbereitet sein, Ihre Architektur in Codeüberprüfungen zu verteidigen (und es zu verteidigen ist eine gute Sache, denn wenn Sie es nicht verteidigen können, sollten Sie es nicht tun).


4
Ich würde argumentieren, dass es nie notwendig und so selten nützlich ist, dass selbst wenn es perfekt passt, die Einsparungen gegenüber der Delegierung die Verwirrung der Programmierer, die es nicht gewohnt sind, aber ansonsten eine gute Zusammenfassung, nicht kompensieren.
Bill K

2
Ich möchte zu Punkt 5 hinzufügen, dass der Code, der MI verwendet, einen Kommentar direkt daneben haben sollte, der den Grund erklärt, damit Sie ihn in einer Codeüberprüfung nicht mündlich erklären müssen. Ohne dies kann jemand, der nicht Ihr Rezensent ist, Ihr gutes Urteilsvermögen in Frage stellen, wenn er Ihren Code sieht, und Sie haben möglicherweise keine Gelegenheit, ihn zu verteidigen.
Tim Abell

Weil du dem oben beschriebenen Diamond of Dread nicht begegnen wirst. “ Warum nicht?
Neugieriger

1
Ich benutze MI für das Beobachtermuster.
Calmarius

13
@ BillK: Natürlich ist es nicht notwendig. Wenn Sie eine Turning-Sprache ohne MI haben, können Sie alles tun, was eine Sprache mit MI kann. Also ja, das ist nicht nötig. Je. Das heißt, es kann ein sehr nützliches Werkzeug sein, und der sogenannte gefürchtete Diamant ... Ich habe nie wirklich verstanden, warum das Wort "gefürchtet" überhaupt dort ist. Es ist eigentlich ziemlich einfach zu argumentieren und normalerweise kein Problem.
Thomas Eding

145

Aus einem Interview mit Bjarne Stroustrup :

Die Leute sagen ganz richtig, dass Sie keine Mehrfachvererbung benötigen, denn alles, was Sie mit Mehrfachvererbung tun können, können Sie auch mit Einzelvererbung tun. Sie verwenden nur den von mir erwähnten Delegationstrick. Darüber hinaus benötigen Sie überhaupt keine Vererbung, da alles, was Sie mit Einzelvererbung tun, auch ohne Vererbung durch Weiterleiten durch eine Klasse erfolgen kann. Eigentlich brauchen Sie auch keine Klassen, weil Sie alles mit Zeigern und Datenstrukturen machen können. Aber warum willst du das tun? Wann ist es bequem, die Sprachfunktionen zu nutzen? Wann würden Sie eine Problemumgehung bevorzugen? Ich habe Fälle gesehen, in denen Mehrfachvererbung nützlich ist, und ich habe sogar Fälle gesehen, in denen ziemlich komplizierte Mehrfachvererbung nützlich ist. Im Allgemeinen bevorzuge ich die von der Sprache angebotenen Funktionen, um Problemumgehungen durchzuführen


6
Es gibt einige Funktionen von C ++, die ich in C # und Java vermisst habe, obwohl sie das Design schwieriger / komplizierter machen würden. Zum Beispiel können die Unveränderlichkeit Garantien const- habe ich musste klobig Abhilfen schreiben ( in der Regel Schnittstellen und Zusammensetzung verwendet wird ) , wenn eine Klasse wirklich benötigt mutable- und unveränderlich-Varianten zu haben. Ich habe jedoch noch nie eine Mehrfachvererbung verpasst und hatte nie das Gefühl, dass ich aufgrund des Fehlens dieser Funktion eine Problemumgehung schreiben musste. Das ist der Unterschied. In jedem Fall habe ich je gesehen nicht mit MI ist die bessere Wahl Design, keine Abhilfe.
BlueRaja - Danny Pflughoeft

24
+1: Perfekte Antwort: Wenn Sie es nicht verwenden möchten, verwenden Sie es nicht.
Thomas Eding

38

Es gibt keinen Grund, dies zu vermeiden, und es kann in Situationen sehr nützlich sein. Sie müssen sich jedoch der potenziellen Probleme bewusst sein.

Der größte ist der Diamant des Todes:

class GrandParent;
class Parent1 : public GrandParent;
class Parent2 : public GrandParent;
class Child : public Parent1, public Parent2;

Sie haben jetzt zwei "Kopien" von GrandParent in Child.

C ++ hat jedoch darüber nachgedacht und ermöglicht Ihnen die virtuelle Vererbung, um die Probleme zu umgehen.

class GrandParent;
class Parent1 : public virtual GrandParent;
class Parent2 : public virtual GrandParent;
class Child : public Parent1, public Parent2;

Überprüfen Sie immer Ihr Design und stellen Sie sicher, dass Sie keine Vererbung verwenden, um bei der Wiederverwendung von Daten zu sparen. Wenn Sie dasselbe mit Komposition darstellen können (und normalerweise auch), ist dies ein weitaus besserer Ansatz.


21
Und wenn Sie die Vererbung verbieten und stattdessen nur die Komposition verwenden, haben Sie immer zwei GrandParentin Child. Es besteht die Angst vor MI, weil die Leute einfach denken, sie könnten die Sprachregeln nicht verstehen. Aber wer diese einfachen Regeln nicht bekommen kann, kann auch kein nicht triviales Programm schreiben.
Neugieriger

1
Das klingt hart, C ++ - Regeln sind rundum sehr ausgefeilt. OK, selbst wenn die einzelnen Regeln einfach sind, gibt es viele davon, was das Ganze nicht einfach macht, zumindest für einen Menschen.
Zebrafisch

11

Siehe w: Mehrfachvererbung .

Mehrfachvererbung wurde kritisiert und ist daher in vielen Sprachen nicht implementiert. Kritik umfasst:

  • Erhöhte Komplexität
  • Semantische Ambiguität wird oft als Diamantproblem zusammengefasst .
  • Es ist nicht möglich, mehrere Male explizit von einer einzelnen Klasse zu erben
  • Reihenfolge der Vererbung Änderung der Klassensemantik.

Die Mehrfachvererbung in Sprachen mit Konstruktoren im C ++ / Java-Stil verschärft das Vererbungsproblem von Konstruktoren und der Verkettung von Konstruktoren, wodurch Wartungs- und Erweiterungsprobleme in diesen Sprachen entstehen. Objekte in Vererbungsbeziehungen mit sehr unterschiedlichen Konstruktionsmethoden sind unter dem Konstruktorkettenparadigma schwer zu implementieren.

Moderne Methode zur Lösung dieses Problems, um eine Schnittstelle (reine abstrakte Klasse) wie die COM- und Java-Schnittstelle zu verwenden.

Ich kann stattdessen andere Dinge tun?

Ja, du kannst. Ich werde von GoF stehlen .

  • Programmieren Sie auf eine Schnittstelle, nicht auf eine Implementierung
  • Ziehen Sie die Komposition der Vererbung vor

8

Öffentliche Vererbung ist eine IS-A-Beziehung, und manchmal ist eine Klasse eine Art von mehreren verschiedenen Klassen, und manchmal ist es wichtig, dies zu reflektieren.

"Mixins" sind manchmal auch nützlich. Es handelt sich im Allgemeinen um kleine Klassen, die normalerweise nichts erben und nützliche Funktionen bieten.

Solange die Vererbungshierarchie ziemlich flach ist (wie es fast immer sein sollte) und gut verwaltet wird, ist es unwahrscheinlich, dass Sie die gefürchtete Diamantvererbung erhalten. Der Diamant ist nicht bei allen Sprachen ein Problem, die Mehrfachvererbung verwenden, aber die Behandlung von C ++ ist häufig umständlich und manchmal rätselhaft.

Ich bin zwar auf Fälle gestoßen, in denen Mehrfachvererbung sehr praktisch ist, aber sie sind tatsächlich ziemlich selten. Dies liegt wahrscheinlich daran, dass ich andere Entwurfsmethoden bevorzuge, wenn ich nicht wirklich Mehrfachvererbung benötige. Ich ziehe es vor, verwirrende Sprachkonstrukte zu vermeiden, und es ist einfach, Vererbungsfälle zu konstruieren, in denen Sie das Handbuch wirklich gut lesen müssen, um herauszufinden, was los ist.


6

Sie sollten Mehrfachvererbung nicht "vermeiden", aber Sie sollten sich der Probleme bewusst sein, die auftreten können, wie z. B. das "Diamantproblem" ( http://en.wikipedia.org/wiki/Diamond_problem ), und die Ihnen übertragene Kraft mit Sorgfalt behandeln , wie du es mit allen Kräften tun solltest.


1
+1: So wie Sie Zeiger nicht vermeiden sollten; Sie müssen sich nur ihrer Auswirkungen bewusst sein. Die Leute müssen aufhören, Sätze abzurasseln (wie "MI ist böse", "nicht optimieren", "gehe ist böse"), die sie aus dem Internet gelernt haben, nur weil einige Hippies sagten, es sei schlecht für ihre Hygiene. Das Schlimmste ist, dass sie noch nie versucht haben, solche Dinge zu benutzen und es einfach so nehmen, als ob es aus der Bibel gesprochen würde.
Thomas Eding

1
Genau. Vermeiden Sie es nicht, sondern kennen Sie den besten Weg, um das Problem zu lösen. Vielleicht würde ich lieber kompositorische Merkmale verwenden, aber C ++ hat das nicht. Vielleicht würde ich lieber die automatisierte Delegierung "Handles" verwenden, aber C ++ hat das nicht. Deshalb benutze ich das allgemeine und stumpfe Werkzeug von MI für verschiedene Zwecke, vielleicht gleichzeitig.
JDługosz

3

Auf die Gefahr hin, ein bisschen abstrakt zu werden, finde ich es aufschlussreich, über Vererbung im Rahmen der Kategorietheorie nachzudenken.

Wenn wir an all unsere Klassen und Pfeile zwischen ihnen denken, die Vererbungsbeziehungen bezeichnen, dann so etwas

A --> B

bedeutet, dass class Babgeleitet von class A. Beachten Sie, dass gegeben

A --> B, B --> C

wir sagen, C leitet sich von B ab, das von A abgeleitet ist, also soll C auch von A abgeleitet sein, also

A --> C

Darüber hinaus sagen wir, dass unser Vererbungsmodell für jede Klasse A, von der es trivial Aabgeleitet ist A, die Definition einer Kategorie erfüllt. In der traditionelleren Sprache haben wir eine Kategorie Classmit Objekten aller Klassen und Morphismen der Vererbungsbeziehungen.

Das ist ein bisschen Setup, aber damit werfen wir einen Blick auf unseren Diamond of Doom:

C --> D
^     ^
|     |
A --> B

Es ist ein schattig aussehendes Diagramm, aber es wird reichen. So Derbt von allen A, Bund C. Und wenn man sich der Frage von OP nähert, Derbt man auch von jeder Oberklasse von A. Wir können ein Diagramm zeichnen

C --> D --> R
^     ^
|     |
A --> B
^ 
|
Q

Nun, Probleme, die hier mit dem Diamanten des Todes verbunden sind, sind, wann Cund Bteilen einige Eigenschafts- / Methodennamen und Dinge mehrdeutig werden; Wenn wir jedoch ein gemeinsames Verhalten verschieben, Averschwindet die Mehrdeutigkeit.

Setzen Sie in kategorisch, wir wollen A, Bund Cso zu sein , dass , wenn Bund Cvererben Qdann Awie als Unterklasse überschrieben werden können Q. Dies macht so Aetwas wie einen Pushout .

Es gibt auch eine symmetrische Konstruktion, Ddie als Pullback bezeichnet wird . Dies ist im Wesentlichen die allgemeinste nützliche Klasse, die Sie erstellen können und die sowohl von Bals auch von erbt C. Das heißt, wenn Sie eine andere Klasse haben, die Rmehrfach von Bund erbt C, dann Dist dies eine Klasse, in Rdie als Unterklasse von umgeschrieben werden kann D.

Wenn Sie sicherstellen, dass Ihre Spitzen des Diamanten Pullbacks und Pushouts sind, können wir Probleme mit Namenskonflikten oder Wartungsarbeiten, die andernfalls auftreten könnten, generisch behandeln.

Hinweis Paercebal ‚s Antwort inspiriert dies als seine Ermahnungen durch das obige Modell gegeben impliziert werden , dass wir in der vollen Kategorie arbeiten Klasse aller möglichen Klassen.

Ich wollte sein Argument auf etwas verallgemeinern, das zeigt, wie kompliziert Mehrfachvererbungsbeziehungen sowohl mächtig als auch unproblematisch sein können.

TL; DR Stellen Sie sich die Vererbungsbeziehungen in Ihrem Programm als eine Kategorie vor. Dann können Sie Diamond of Doom-Probleme vermeiden, indem Sie mehrfach vererbte Klassen-Pushouts und symmetrisch erstellen und eine gemeinsame übergeordnete Klasse erstellen, die ein Pullback ist.


3

Wir benutzen Eiffel. Wir haben einen ausgezeichneten MI. Keine Sorge. Keine Probleme. Leicht zu handhaben. Es gibt Zeiten, in denen MI NICHT verwendet wird. Es ist jedoch nützlicher, als die Leute glauben, weil sie: A) in einer gefährlichen Sprache sind, die es nicht gut beherrscht -OR- B) zufrieden damit sind, wie sie jahrelang mit MI gearbeitet haben -OR- C) aus anderen Gründen ( zu zahlreich, um sie aufzulisten Ich bin mir ziemlich sicher - siehe Antworten oben).

Für uns ist MI mit Eiffel so natürlich wie alles andere und ein weiteres gutes Werkzeug in der Toolbox. Ehrlich gesagt sind wir nicht besorgt darüber, dass sonst niemand Eiffel benutzt. Keine Sorge. Wir sind zufrieden mit dem, was wir haben und laden Sie ein, einen Blick darauf zu werfen.

Während Sie suchen: Beachten Sie besonders die Leeren-Sicherheit und die Beseitigung der Null-Zeiger-Dereferenzierung. Während wir alle um MI herum tanzen, gehen Ihre Zeiger verloren! :-)


2

Jede Programmiersprache hat eine etwas andere Behandlung der objektorientierten Programmierung mit Vor- und Nachteilen. Die Version von C ++ legt den Schwerpunkt auf die Leistung und hat den damit verbundenen Nachteil, dass es störend einfach ist, ungültigen Code zu schreiben - und dies gilt für die Mehrfachvererbung. Infolgedessen besteht die Tendenz, Programmierer von dieser Funktion wegzulenken.

Andere Leute haben sich mit der Frage befasst, wozu Mehrfachvererbung nicht gut ist. Aber wir haben einige Kommentare gesehen, die mehr oder weniger implizieren, dass der Grund, dies zu vermeiden, darin besteht, dass es nicht sicher ist. Ja und nein.

Wie es in C ++ häufig der Fall ist, können Sie eine grundlegende Richtlinie sicher verwenden, ohne ständig über die Schulter schauen zu müssen. Die Schlüsselidee ist, dass Sie eine spezielle Art von Klassendefinition unterscheiden, die als "Mix-In" bezeichnet wird. Klasse ist ein Mix-In, wenn alle ihre Mitgliedsfunktionen virtuell (oder rein virtuell) sind. Dann dürfen Sie von einer einzelnen Hauptklasse und so vielen "Mix-Ins" erben, wie Sie möchten - aber Sie sollten Mixins mit dem Schlüsselwort "virtual" erben. z.B

class CounterMixin {
    int count;
public:
    CounterMixin() : count( 0 ) {}
    virtual ~CounterMixin() {}
    virtual void increment() { count += 1; }
    virtual int getCount() { return count; }
};

class Foo : public Bar, virtual public CounterMixin { ..... };

Mein Vorschlag ist, dass Sie, wenn Sie beabsichtigen, eine Klasse als Mix-In-Klasse zu verwenden, auch eine Namenskonvention anwenden, um es jedem, der den Code liest, zu erleichtern, zu sehen, was passiert, und um zu überprüfen, ob Sie die Regeln der grundlegenden Richtlinie einhalten . Und Sie werden feststellen, dass es viel besser funktioniert, wenn Ihre Mix-Ins auch Standardkonstruktoren haben, nur weil virtuelle Basisklassen funktionieren. Und denken Sie daran, alle Destruktoren auch virtuell zu machen.

Beachten Sie, dass meine Verwendung des Wortes "Mix-In" hier nicht mit der parametrisierten Vorlagenklasse identisch ist ( eine gute Erklärung finden Sie unter diesem Link ), aber ich denke, dass dies eine faire Verwendung der Terminologie ist.

Jetzt möchte ich nicht den Eindruck erwecken, dass dies die einzige Möglichkeit ist, Mehrfachvererbung sicher zu verwenden. Es ist nur eine Möglichkeit, die ziemlich einfach zu überprüfen ist.


2

Sie sollten es vorsichtig verwenden, es gibt einige Fälle, wie das Diamantproblem , in denen die Dinge kompliziert werden können.

Alt-Text
(Quelle: learncpp.com )


12
Dies ist ein schlechtes Beispiel, da ein Kopierer aus einem Scanner und einem Drucker bestehen und nicht von diesen erben sollte.
Svante

2
Jedenfalls Printersollte a nicht einmal a sein PoweredDevice. A Printerdient zum Drucken und nicht zur Energieverwaltung. Die Implementierung eines bestimmten Druckers muss möglicherweise eine Energieverwaltung durchführen, diese Energieverwaltungsbefehle sollten jedoch nicht direkt den Benutzern des Druckers zugänglich gemacht werden. Ich kann mir keine reale Verwendung dieser Hierarchie vorstellen.
Neugieriger


1

Über das Rautenmuster hinaus erschwert die Mehrfachvererbung das Verständnis des Objektmodells, was wiederum die Wartungskosten erhöht.

Komposition ist an sich leicht zu verstehen, zu verstehen und zu erklären. Das Schreiben von Code kann mühsam werden, aber eine gute IDE (es ist ein paar Jahre her, seit ich mit Visual Studio gearbeitet habe, aber sicherlich haben alle Java-IDEs großartige Tools zur Automatisierung von Kompositionsverknüpfungen) sollte Sie über diese Hürde bringen.

Auch in Bezug auf die Wartung tritt das "Diamantproblem" auch in nicht wörtlichen Vererbungsinstanzen auf. Wenn Sie zum Beispiel A und B haben und Ihre Klasse C beide erweitert, und A eine 'makeJuice'-Methode hat, mit der Orangensaft hergestellt wird, und Sie diese Methode erweitern, um Orangensaft mit einem Hauch Limette herzustellen: Was passiert, wenn der Designer für' B 'fügt eine' makeJuice'-Methode hinzu, die elektrischen Strom erzeugt? ‚A‘ und ‚B‘ kann kompatibel „Eltern“ sein gerade jetzt , aber dass sie sein wird , bedeutet nicht immer so!

Insgesamt ist die Maxime, eine Vererbung und insbesondere eine Mehrfachvererbung zu vermeiden, stichhaltig. Wie bei allen Maximen gibt es Ausnahmen, aber Sie müssen sicherstellen, dass ein blinkendes grünes Neonschild auf alle von Ihnen codierten Ausnahmen zeigt (und Ihr Gehirn so trainieren, dass Sie jedes Mal, wenn Sie solche Erbbäume sehen, in Ihrem eigenen blinkenden grünen Neon zeichnen Zeichen), und dass Sie von Zeit zu Zeit überprüfen, ob alles Sinn macht.


what happens when the designer for 'B' adds a 'makeJuice' method which generates and electrical current?Uhhh, Sie erhalten natürlich einen Kompilierungsfehler (wenn die Verwendung nicht eindeutig ist).
Thomas Eding

1

Das Hauptproblem bei MI von konkreten Objekten ist, dass Sie selten ein Objekt haben, das zu Recht "ein A sein und ein B sein sollte", so dass es aus logischen Gründen selten die richtige Lösung ist. Viel häufiger haben Sie ein Objekt C, das "C kann als A oder B fungieren" befolgt, was Sie über die Vererbung und Zusammensetzung der Schnittstelle erreichen können. Aber machen Sie keinen Fehler - die Vererbung mehrerer Schnittstellen ist immer noch MI, nur eine Teilmenge davon.

Insbesondere für C ++ ist die Hauptschwäche des Features nicht die tatsächliche EXISTENCE of Multiple Inheritance, sondern einige Konstrukte, die es zulässt und die fast immer fehlerhaft sind. Zum Beispiel das Erben mehrerer Kopien desselben Objekts wie:

class B : public A, public A {};

ist durch Definition missgebildet. Übersetzt ins Englische ist dies "B ist ein A und ein A". Selbst in der menschlichen Sprache gibt es also eine starke Mehrdeutigkeit. Meinten Sie "B hat 2 As" oder nur "B ist ein A"?. Das Zulassen eines solchen pathologischen Codes und noch schlimmer ein Anwendungsbeispiel hat C ++ keinen Gefallen getan, wenn es darum ging, die Funktion in Nachfolgesprachen beizubehalten.


0

Sie können die Komposition der Vererbung vorziehen.

Das allgemeine Gefühl ist, dass die Komposition besser ist und sehr gut diskutiert wird.


4
-1: The general feeling is that composition is better, and it's very well discussed.Das heißt nicht, dass die Komposition besser ist.
Thomas Eding

Nur dass es tatsächlich besser ist.
Ali Afshar

12
Außer in den Fällen, in denen es nicht besser ist.
Thomas Eding

0

Es dauert 4/8 Bytes pro betroffener Klasse. (Einer dieser Zeiger pro Klasse).

Dies ist vielleicht nie ein Problem, aber wenn Sie eines Tages eine Mikrodatenstruktur haben, die milliardenfach instanziiert ist, wird dies der Fall sein.


1
Dessen sicher? Normalerweise erfordert jede ernsthafte Vererbung einen Zeiger in jedem Klassenmitglied, aber skaliert dies notwendigerweise mit geerbten Klassen? Genauer gesagt, wie oft hatten Sie Milliarden von Teeny-Datenstrukturen?
David Thornley

3
@DavidThornley " Wie oft hatten Sie Milliarden von Teeny-Datenstrukturen? " Wenn Sie sich Argumente gegen MI ausdenken.
Neugieriger
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.