Anwendungsfälle für die mehrfache Vererbung

15

Java lässt die Mehrfachvererbung aus dem Grund aus, dass das Designziel, die Sprache einfach zu halten, umgangen wird .

Ich frage mich, ob Java (mit seinem Ökosystem) wirklich "einfach" ist. Python ist nicht komplex und mehrfach vererbt. Also ohne zu subjektiv zu sein, ist meine Frage ...

Was sind die typischen Problemmuster, die von einem Code profitieren, der so konzipiert ist, dass er vielfache Vererbung nutzt?

Baumkodierer
quelle
8
Java lässt aus sehr wenigen Gründen eine Menge absolut guter Dinge aus. Ich würde keine anständige Rechtfertigung für MI erwarten.
DeadMG
2
Pythons Mehrfachvererbung ist definitiv das Gebiet der Here-Be-Dragons . Die Tatsache, dass die Namensauflösung von links nach rechts von der Tiefe zuerst verwendet wird, hat erhebliche Probleme sowohl für die Wartbarkeit als auch für das Verständnis. Während es in flachen Klassenhierarchien nützlich sein kann, kann es in tiefen unglaublich kontraintuitiv sein.
Mark Booth
Ich denke, der Grund, warum Java keine Mehrfachvererbung enthält, ist, dass die Java-Entwickler wollten, dass ihre Sprache leicht zu erlernen ist. Die mehrfache Vererbung ist zwar in einigen Fällen unglaublich mächtig, aber schwer zu erfassen und noch schwerer zu nutzen, um eine gute Wirkung zu erzielen. Es ist nicht das, womit man einen Programmier-Neuling konfrontieren möchte. Ich meine: Wie erklären Sie jemandem, der mit dem Konzept der Vererbung selbst zu kämpfen hat, die virtuelle Vererbung? Und da die mehrfache Vererbung auch auf der Seite der Implementierer nicht gerade trivial ist, ist es wahrscheinlich, dass der Java-Entwickler eine Win-Win-Situation hat, wenn er sie weglässt.
cmaster
Java ist nominal geschrieben. Python ist nicht. Dies macht die Mehrfachvererbung in Python einfacher zu implementieren und zu verstehen.
Jules

Antworten:

11

Vorteile:

  1. Es ermöglicht manchmal eine offensichtlichere Modellierung eines Problems als andere Möglichkeiten, es zu modellieren.
  2. Wenn die verschiedenen Eltern einen orthogonalen Zweck haben, kann dies eine Art Compositing ermöglichen

Nachteile:

  1. Wenn die verschiedenen Eltern keinen orthogonalen Zweck haben, ist der Typ nur schwer zu verstehen.
  2. Es ist nicht leicht zu verstehen, wie es in einer Sprache (einer beliebigen Sprache) implementiert ist.

In C ++ ist ein gutes Beispiel für die Mehrfachvererbung, mit der orthogonale Features zusammengesetzt werden, wenn Sie CRTP verwenden , um beispielsweise ein Komponentensystem für ein Spiel einzurichten.

Ich habe angefangen, ein Beispiel zu schreiben, aber ich denke, ein Beispiel aus der realen Welt ist es wert, angeschaut zu werden. Ein Teil des Ogre3D-Codes verwendet die Mehrfachvererbung auf eine nette und sehr intuitive Art und Weise. Beispielsweise erbt die Mesh- Klasse sowohl von Resources als auch von AnimationContainer. Ressourcen machen die allen Ressourcen gemeinsame Schnittstelle verfügbar, und AnimationContainer macht die Schnittstelle verfügbar, die für die Bearbeitung einer Reihe von Animationen spezifisch ist. Sie sind nicht miteinander verwandt, daher ist es einfach, sich ein Mesh als Ressource vorzustellen, die zusätzlich eine Reihe von Animationen enthalten kann. Fühlt sich natürlich an, nicht wahr?

Sie können sich andere Beispiele in dieser Bibliothek ansehen , z. B. die Art und Weise , wie die Speicherzuweisung auf eine abgestimmte Weise verwaltet wird, indem Klassen von Varianten einer CRTP-Klasse geerbt werden, die neu überladen und gelöscht werden.

Wie bereits erwähnt, ergeben sich die Hauptprobleme bei der Mehrfachvererbung aus der Vermischung verwandter Konzepte. Dadurch muss die Sprache komplexe Implementierungen festlegen (siehe die Art und Weise, wie C ++ mit dem Diamond-Problem spielt ...), und der Benutzer ist sich nicht sicher, was in dieser Implementierung passiert. Lesen Sie beispielsweise diesen Artikel, in dem erläutert wird, wie er in C ++ implementiert ist .

Wenn Sie es aus der Sprache entfernen, vermeiden Sie, dass Personen, die nicht wissen, wie die Sprache dazu gezwungen wird, die Dinge zu verschlechtern. Aber es zwingt zu einer Denkweise, die sich manchmal nicht natürlich anfühlt, selbst wenn es Randfälle sind, kommt es öfter vor, als Sie vielleicht denken.

Klaim
quelle
Ich würde mich sehr freuen, wenn Sie Ihre Antwort mit einem Beispielproblem schmücken würden - das wird Begriffe wie "orthogonal purpose" klarer machen - aber danke
Treecoder
Ok, lass mich versuchen, etwas hinzuzufügen.
Klaim
Ogre3D ist kein Ort, an dem ich nach Design-Inspirationen Ausschau halten würde - haben Sie schon die Singleton-Infektion gesehen?
DeadMG
Erstens ist Erben-Singleton nicht wirklich ein Singleton, die Konstruktion und Zerstörung ist explizit. Als nächstes ist Ogre eine Schicht über einem Hardwaresystem (oder dem Grafiktreiber, wenn Sie es vorziehen). Das heißt, es sollte nur eine eindeutige Darstellung für Systemschnittstellen geben (wie Root oder andere). Sie können einige Singleton entfernen, aber das ist hier nicht der Punkt. Ich habe es freiwillig vermieden, darauf hinzuweisen, um eine Trolldiskussion zu vermeiden. Schauen Sie sich also bitte die Beispiele an, auf die ich hingewiesen habe. Ihre Verwendung von Singleton mag nicht perfekt sein, aber es ist in der Praxis eindeutig nützlich (aber nur für ihre Art von System nicht alles).
Klaim
4

Es gibt ein Konzept namens Mixins , das in dynamischeren Sprachen häufig verwendet wird. Mehrfachvererbung ist eine Möglichkeit, mit der Mixins von einer Sprache unterstützt werden können. Mixins werden im Allgemeinen als Methode für eine Klasse verwendet, um verschiedene Funktionalitäten zu akkumulieren. Ohne Mehrfachvererbung müssen Sie Aggregation / Delegation verwenden, um das Verhalten von Mixin-Typen mit einer Klasse zu erhalten, die etwas syntaktisch anspruchsvoller ist.

RationalGeek
quelle
+1 Dies ist eigentlich ein guter Grund, Mehrfachvererbung zu haben. Mixins enthalten zusätzliche Konnotationen ("Diese Klasse sollte nicht als eigenständige Klasse verwendet werden")
ashes999
2

Ich denke, die Wahl basiert hauptsächlich auf Problemen aufgrund des Diamantenproblems .

Darüber hinaus ist es häufig möglich, die Verwendung der Mehrfachvererbung durch Delegation oder auf andere Weise zu umgehen.

Ich bin mir nicht sicher, was deine letzte Frage bedeutet. Aber wenn es "in welchen Fällen ist Mehrfachvererbung sinnvoll?" Ist, dann in allen Fällen, in denen Sie ein Objekt A mit Funktionen der Objekte B und C haben möchten.

dagnelies
quelle
2

Ich werde hier nicht weiter darauf eingehen, aber Sie können die Mehrfachvererbung in Python sicher über den folgenden Link verstehen : http://docs.python.org/release/1.5.1p1/tut/multiple.html :

Die einzige Regel, die zur Erläuterung der Semantik erforderlich ist, ist die Auflösungsregel, die für Verweise auf Klassenattribute verwendet wird. Dies ist Tiefe zuerst, von links nach rechts. Wenn also ein Attribut in DerivedClassName nicht gefunden wird, wird es in Base1 und dann (rekursiv) in den Basisklassen von Base1 gesucht. Nur wenn es dort nicht gefunden wird, wird es in Base2 gesucht und so weiter.

...

Es ist klar, dass die wahllose Verwendung von Mehrfachvererbung ein Alptraum für die Wartung ist, da sich Python auf Konventionen stützt, um versehentliche Namenskonflikte zu vermeiden. Ein bekanntes Problem bei der Mehrfachvererbung ist eine Klasse, die aus zwei Klassen abgeleitet wurde, die zufällig eine gemeinsame Basisklasse haben. Obwohl es einfach genug ist, herauszufinden, was in diesem Fall passiert (die Instanz verfügt über eine einzige Kopie von Instanzvariablen oder Datenattributen, die von der allgemeinen Basisklasse verwendet werden), ist nicht klar, ob diese Semantik in irgendeiner Weise vorliegt nützlich.

Dies ist nur ein kleiner Absatz, aber groß genug, um die Zweifel, die ich denke, auszuräumen.

Pankaj Upadhyay
quelle
1

Ein Ort, an dem Mehrfachvererbung nützlich wäre, ist eine Situation, in der eine Klasse mehrere Schnittstellen implementiert, Sie jedoch einige Standardfunktionen in jede Schnittstelle integrieren möchten. Dies ist nützlich, wenn die meisten Klassen, die eine Schnittstelle implementieren, etwas auf die gleiche Weise tun möchten, aber gelegentlich etwas anderes tun müssen. Sie können jede Klasse mit derselben Implementierung haben, aber es ist sinnvoller, sie an einen Ort zu verschieben.

KeithB
quelle
1
Wäre dafür eine verallgemeinerte Mehrfachvererbung erforderlich, oder nur eine Methode, mit der eine Schnittstelle Standardverhalten für nicht implementierte Methoden festlegen kann? Wenn Schnittstellen nur Standardimplementierungen für die Methoden angeben könnten, die sie selbst implementieren (im Gegensatz zu denen, die sie von anderen Schnittstellen erben), würde ein solches Feature die Double-Diamond-Probleme vollständig vermeiden, die die Mehrfachvererbung erschweren.
Supercat
1

Was sind die typischen Problemmuster, die von einem Code profitieren, der so konzipiert ist, dass er vielfache Vererbung nutzt?

Dies ist nur ein Beispiel, das für mich von unschätzbarem Wert ist, um die Sicherheit zu verbessern und die Versuchung zu lindern, kaskadierende Änderungen für Anrufer oder Unterklassen anzuwenden.

Wo ich mehrfache Vererbung für unglaublich nützlich befunden habe, selbst für die abstraktesten, zustandslosen Schnittstellen, ist die nicht-virtuelle Schnittstellen-IDiom (NVI) in C ++.

Sie sind nicht einmal wirklich abstrakte Basisklassen so viel wie Schnittstellen , die nur noch ein wenig Implementierung ihnen die universellen Aspekte ihrer Verträge zu erzwingen, da sie nicht wirklich die Allgemeingültigkeit des Vertrages so viel wie besser verengenden Strafvollzug .

Einfaches Beispiel (einige könnten überprüfen, ob ein übergebenes Dateihandle offen ist oder so ähnlich):

// Non-virtual interface (public methods are nonvirtual/final).
// Since these are modeling the concept of "interface", not ABC,
// multiple will often be inherited ("implemented") by a subclass.
class SomeInterface
{
public:
    // Pre: x should always be greater than or equal to zero.
    void f(int x) /*final*/
    {
        // Make sure x is actually greater than or equal to zero
        // to meet the necessary pre-conditions of this function.
        assert(x >= 0);

        // Call the overridden function in the subtype.
        f_impl(x);
    }

protected:
    // Overridden by a boatload of subtypes which implement
    // this non-virtual interface.
    virtual void f_impl(int x) = 0;
};

In diesem Fall wird es möglicherweise fvon tausend Stellen in der Codebasis aufgerufen, während f_imples von hundert Unterklassen überschrieben wird.

Es wäre schwierig, diese Art von Sicherheitsprüfung an allen 1000 Orten durchzuführen, die anrufen, foder an allen 100 Orten, die außer Kraft gesetzt werden f_impl.

Indem ich nur diesen Einstiegspunkt für die Funktionalität nicht-virtuell mache, habe ich einen zentralen Ort, an dem ich diese Prüfung durchführen kann. Und diese Prüfung reduziert die Abstraktion nicht im Geringsten, da sie lediglich eine Voraussetzung für den Aufruf dieser Funktion darstellt. In gewisser Weise stärkt dies den Vertrag, der von der Schnittstelle bereitgestellt wird, und verringert die Belastung durch die Überprüfung der xEingabe, um sicherzustellen, dass sie an allen 100 Stellen, an denen sie außer Kraft gesetzt wird, den gültigen Voraussetzungen entspricht.

Ich wünschte, jede Sprache hätte und wünschte sich auch in C ++, dass es sich eher um ein natives Konzept handelt (z. B .: Wir müssten keine separate Funktion zum Überschreiben definieren).

Dies ist äußerst nützlich, wenn Sie dies nicht assertim Voraus getan haben und festgestellt haben, dass Sie es später benötigen, wenn an zufälligen Stellen in der Codebasis negative Werte übergeben werden f.


quelle
0

Erstens: Mehrere Kopien der Basisklasse (ein C ++ - Problem) und enge Kopplung zwischen Basis- und abgeleiteten Klassen.

Zweitens: Mehrfachvererbung von abstrakten Schnittstellen

quant_dev
quelle
Schlagen Sie vor, dass es in keinem Zusammenhang nützlich ist? Und dass alles bequem ohne entworfen / codiert werden kann? Bitte erläutern Sie auch den zweiten Punkt.
Treecoder