Cas d'utilisation de l'héritage multiple

15

Java omet l'héritage multiple au motif qu'il évite l'objectif de conception de garder le langage simple .

Je me demande si Java (avec son écosystème) est vraiment "simple". Python n'est pas complexe et possède plusieurs héritages. Donc, sans être trop subjectif, ma question est ...

Quels sont les modèles de problèmes typiques qui bénéficient d'un code conçu pour faire un usage intensif de l'héritage multiple

treecoder
la source
8
Java omet énormément de choses parfaitement bonnes pour très peu de raisons. Je ne m'attendrais pas à une justification décente de l'IM.
DeadMG
2
L'héritage multiple de Python est définitivement le territoire des dragons . Le fait qu'il utilise la résolution de noms de profondeur en premier, de gauche à droite, pose des problèmes importants pour la maintenabilité et la compréhension. Bien qu'il puisse être utile dans les hiérarchies de classes superficielles, il peut être incroyablement contre-intuitif dans les hiérarchies profondes.
Mark Booth du
Je pense que la raison pour laquelle Java ne contient pas d'héritage multiple est que les développeurs Java voulaient que leur langage soit facile à apprendre. L'héritage multiple, bien qu'incroyablement puissant dans certains cas, est difficile à saisir et encore plus difficile à utiliser à bon escient; ce n'est pas la chose avec laquelle vous voudriez affronter un étudiant de première année en programmation. Je veux dire: comment expliquez-vous l'héritage virtuel à quelqu'un qui se débat avec le concept d'héritage lui-même? Et, comme l'héritage multiple n'est pas non plus vraiment trivial du côté des implémenteurs, le développeur Java est probable que l'omettre soit gagnant-gagnant.
cmaster - réintègre monica
Java est nominalement typé. Python ne l'est pas. Cela rend l'héritage multiple beaucoup plus facile à implémenter et à comprendre en Python.
Jules

Réponses:

11

Avantages :

  1. Il permet parfois une modélisation plus évidente d'un problème que d'autres façons de le modéliser.
  2. Si les différents parents ont une fonction orthogonale, cela peut permettre une sorte de compositing

Les inconvénients :

  1. Si les différents parents n'ont pas de but orthogonal, cela rend le type difficile à comprendre.
  2. Il n'est pas facile de comprendre comment il est implémenté dans une langue (n'importe quelle langue).

En C ++, un bon exemple d'héritage multiple utilisé pour composer des entités orthogonales est lorsque vous utilisez CRTP pour, par exemple, configurer un système de composants pour un jeu.

J'ai commencé à écrire un exemple, mais je pense qu'il vaut mieux regarder un exemple du monde réel. Certains codes d'Ogre3D utilisent l'héritage multiple de manière agréable et très intuitive. Par exemple, la classe Mesh hérite à la fois de Resources et d'AnimationContainer. Les ressources exposent l'interface commune à toutes les ressources et AnimationContainer expose l'interface spécifique pour la manipulation d'un ensemble d'animations. Ils ne sont pas liés, il est donc facile de considérer un maillage comme une ressource qui peut en outre contenir un ensemble d'animations. Sent naturel n'est-ce pas?

Vous pouvez regarder d' autres exemples dans cette bibliothèque , comme la façon dont l'allocation de mémoire est gérée de manière fine en faisant en sorte que les classes héritent des variantes d'une classe CRTP surchargeant new et delete.

Comme nous l'avons dit, les principaux problèmes d'héritage multiple découlent du mélange de concepts liés. Cela oblige le langage à définir des implémentations complexes (voir la façon dont C ++ permet de jouer avec le problème du diamant ...) et l'utilisateur n'est pas sûr de ce qui se passe dans cette implémentation. Par exemple, lisez cet article expliquant comment il est implémenté en C ++ .

Le supprimer de la langue permet d'éviter les personnes qui ne savent pas comment la langue est appliquée pour rendre les choses mauvaises. Mais cela oblige à penser d'une manière qui, parfois, ne semble pas naturelle, même si ce sont des cas marginaux, cela arrive plus souvent que vous ne le pensez.

Klaim
la source
j'apprécierais vraiment si vous orniez votre réponse avec un exemple de problème - qui rendra les termes comme "objectif orthogonal" plus clairs - mais merci
treecoder
Ok, laissez-moi essayer d'ajouter quelque chose.
Klaim
Ogre3D n'est pas un endroit où je chercherais une inspiration pour la conception - avez-vous vu leur infection Singleton?
DeadMG
Premièrement, l'héritier singleton n'est pas vraiment un singleton, la construction et la destruction sont explicites. Ensuite, Ogre est une couche sur un système matériel (ou le pilote graphique si vous préférez). Cela signifie qu'il ne devrait y avoir qu'une seule représentation unique pour les interfaces système (comme Root ou autres). Ils peuvent supprimer certains singleton, mais ce n'est pas le point ici. J'ai volontairement évité de pointer cela pour éviter d'avoir une discussion troll, alors s'il vous plaît, regardez les exemples que j'ai indiqués. Leur utilisation de Singleton n'est peut-être pas parfaite, mais elle est clairement utile dans la pratique (mais uniquement pour leur type de système, pas tout).
Klaim
4

Il existe un concept appelé mixins qui est largement utilisé dans les langages plus dynamiques. L'héritage multiple est une façon dont les mixins peuvent être supportés par un langage. Les mixins sont généralement utilisés comme moyen pour une classe d'accumuler différentes fonctionnalités. Sans héritage multiple, vous devez utiliser l'agrégation / délégation pour obtenir un comportement de type mixin avec une classe, ce qui est un peu plus lourd en syntaxe.

RationalGeek
la source
+1 c'est en fait une bonne raison d'avoir un héritage multiple. Les mixins ont des connotations supplémentaires ("cette classe ne doit pas être utilisée comme une
solution
2

Je pense que le choix est principalement basé sur des problèmes dus au problème du diamant .

De plus, il est souvent possible de contourner l'utilisation de l'héritage multiple par délégation ou par d'autres moyens.

Je ne suis pas sûr du sens de votre dernière question. Mais si c'est "dans quels cas l'héritage multiple est-il utile?", Alors dans tous les cas où vous aimeriez avoir un objet A ayant des fonctionnalités des objets B et C, en gros.

dagnelies
la source
2

Je ne m'étendrai pas beaucoup ici mais vous pouvez sûrement comprendre l'héritage multiple en python via le lien suivant http://docs.python.org/release/1.5.1p1/tut/multiple.html :

La seule règle nécessaire pour expliquer la sémantique est la règle de résolution utilisée pour les références d'attribut de classe. C'est la profondeur d'abord, de gauche à droite. Ainsi, si un attribut n'est pas trouvé dans DerivedClassName, il est recherché dans Base1, puis (récursivement) dans les classes de base de Base1, et seulement s'il n'y est pas trouvé, il est recherché dans Base2, et ainsi de suite.

...

Il est clair que l'utilisation aveugle de l'héritage multiple est un cauchemar de maintenance, étant donné que Python s'appuie sur des conventions pour éviter les conflits de noms accidentels. Un problème bien connu avec l'héritage multiple est une classe dérivée de deux classes qui se trouvent avoir une classe de base commune. Bien qu'il soit assez facile de comprendre ce qui se passe dans ce cas (l'instance aura une seule copie des `` variables d'instance '' ou des attributs de données utilisés par la classe de base commune), il n'est pas clair que ces sémantiques soient en aucune façon utile.

Ce n'est qu'un petit paragraphe mais suffisamment grand pour dissiper les doutes, je suppose.

Pankaj Upadhyay
la source
1

Un endroit où l'héritage multiple serait utile est une situation où une classe implémente plusieurs interfaces, mais vous souhaitez avoir une fonctionnalité par défaut intégrée à chaque interface. Ceci est utile si la plupart des classes qui implémentent une interface veulent faire quelque chose de la même manière, mais parfois vous devez faire quelque chose de différent. Vous pouvez avoir chaque classe avec la même implémentation, mais il est plus logique de la pousser vers le haut en un seul endroit.

KeithB
la source
1
Cela nécessiterait-il un héritage multiple généralisé, ou simplement un moyen par lequel une interface peut spécifier des comportements par défaut pour des méthodes non implémentées? Si les interfaces pouvaient seulement spécifier des implémentations par défaut pour les méthodes qu'elles implémentent elles-mêmes (par opposition à celles qu'elles héritent d'autres interfaces), une telle fonctionnalité éviterait complètement les problèmes de double losange qui rendent difficile l'héritage multiple.
supercat
1

Quels sont les modèles de problèmes typiques qui bénéficient d'un code conçu pour faire un usage intensif de l'héritage multiple?

Ce n'est qu'un exemple, mais celui que je trouve inestimable pour améliorer la sécurité et atténuer les tentations d'appliquer des changements en cascade à travers les appelants ou les sous-classes.

Là où j'ai trouvé l'héritage multiple incroyablement utile, même pour les interfaces sans état les plus abstraites, c'est l'idiome d'interface non virtuelle (NVI) en C ++.

Ce ne sont même pas vraiment des classes de base abstraites autant que des interfaces qui ont juste un peu d'implémentation pour appliquer les aspects universels de leurs contrats, car elles ne réduisent pas vraiment la généralité du contrat autant qu'elles le font mieux. .

Exemple simple (certains pourraient vérifier qu'un descripteur de fichier transmis est ouvert ou quelque chose comme ça):

// 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;
};

Dans ce cas, peut f- être est appelé par mille endroits dans la base de code, tandis quef_impl est remplacé par une centaine de sous-classes.

Il serait difficile de faire ce genre de contrôle de sécurité dans les 1000 lieux qui appellent fou dans les 100 lieuxf_impl .

En ne faisant que ce point d'entrée à la fonctionnalité non virtuelle, cela me donne un endroit central pour effectuer cette vérification. Et cette vérification ne réduit pas du tout l'abstraction, car elle affirme simplement une condition préalable requise pour appeler cette fonction. Dans un sens, cela renforce sans doute le contrat fourni par l'interface et soulage le fardeau de la vérification de l' xentrée pour s'assurer qu'elle est conforme aux conditions préalables valides dans les 100 endroits qui la remplacent.

C'est quelque chose que je souhaite que chaque langage ait, et souhaite également, même en C ++, que ce soit un peu plus un concept natif (ex: ne pas nous obliger à définir une fonction distincte pour remplacer).

Ceci est extrêmement utile si vous ne l'avez pas fait assertà l'avance et que vous vous en êtes rendu compte plus tard lorsque certains endroits aléatoires de la base de code rencontraient des valeurs négatives transmises f.


la source
0

Premièrement: plusieurs copies de la classe de base (un problème C ++) et un couplage étroit entre les classes de base et dérivées.

Deuxièmement: l'héritage multiple des interfaces abstraites

quant_dev
la source
suggérez-vous que ce n'est utile dans aucun contexte? Et que tout peut être conçu / codé facilement sans cela? Veuillez également développer le deuxième point.
treecoder