Pourquoi l'héritage multiple n'est-il pas autorisé en Java ou C #?

115

Je sais que l'héritage multiple n'est pas autorisé en Java et C #. De nombreux livres disent simplement que l'héritage multiple n'est pas autorisé. Mais il peut être implémenté en utilisant des interfaces. Rien n'est discuté sur les raisons pour lesquelles cela n'est pas autorisé. Quelqu'un peut-il me dire précisément pourquoi ce n'est pas autorisé?

Abdulsattar Mohammed
la source
1
Je tiens juste à souligner qu'il existe des frameworks qui permettent un comportement de type MI dans les classes C #. Et bien sûr, vous avez la possibilité d'avoir des mixins sans état (en utilisant des méthodes d'extensions) - étant sans état, ils ne sont pas très utiles cependant.
Dmitri Nesteruk
1
Que les interfaces aient quelque chose à voir avec l'héritage est une illusion créée par la syntaxe du langage. L'héritage est censé vous rendre plus riche. Rien de tel pour une interface, vous n'héritez pas de squat et cela vous rend nettement plus pauvre. Vous avez hérité de la dette de jeu de l'oncle Harry, vous avez encore du travail à faire pour implémenter l'interface.
Hans Passant

Réponses:

143

La réponse courte est: parce que les concepteurs de langage ont décidé de ne pas le faire.

Fondamentalement, il semblait que les concepteurs .NET et Java n'autorisaient pas l'héritage multiple car ils pensaient que l'ajout de MI ajoutait trop de complexité aux langages tout en offrant trop peu d'avantages .

Pour une lecture plus ludique et approfondie, il y a quelques articles disponibles sur le web avec des interviews de certains des designers de langage. Par exemple, pour .NET, Chris Brumme (qui a travaillé chez MS sur le CLR) a expliqué les raisons pour lesquelles ils ont décidé de ne pas:

  1. Différentes langues ont en fait des attentes différentes quant au fonctionnement de MI. Par exemple, comment les conflits sont résolus et si les bases dupliquées sont fusionnées ou redondantes. Avant de pouvoir implémenter MI dans le CLR, nous devons faire une étude de toutes les langues, déterminer les concepts communs et décider comment les exprimer de manière neutre. Il faudrait également décider si MI appartient au CLS et ce que cela signifierait pour les langages qui ne veulent pas de ce concept (vraisemblablement VB.NET, par exemple). Bien sûr, c'est notre métier en tant que runtime de langage commun, mais nous n'avons pas encore réussi à le faire pour MI.

  2. Le nombre d'endroits où l'IM est vraiment approprié est en fait assez faible. Dans de nombreux cas, l'héritage d'interfaces multiples peut faire le travail à la place. Dans d'autres cas, vous pourrez peut-être utiliser l'encapsulation et la délégation. Si nous devions ajouter une construction légèrement différente, comme les mixins, serait-ce vraiment plus puissant?

  3. L'héritage d'implémentations multiples injecte beaucoup de complexité dans l'implémentation. Cette complexité a un impact sur la diffusion, la mise en page, la répartition, l'accès sur le terrain, la sérialisation, les comparaisons d'identité, la vérifiabilité, la réflexion, les génériques et probablement beaucoup d'autres endroits.

Vous pouvez lire l'article entier ici.

Pour Java, vous pouvez lire cet article :

Les raisons d'omettre l'héritage multiple du langage Java proviennent principalement de l'objectif «simple, orienté objet et familier». En tant que langage simple, les créateurs de Java voulaient un langage que la plupart des développeurs pourraient maîtriser sans formation approfondie. À cette fin, ils ont travaillé pour rendre le langage aussi similaire que possible au C ++ (familier) sans porter sur la complexité inutile du C ++ (simple).

De l'avis des concepteurs, l'héritage multiple cause plus de problèmes et de confusion qu'il n'en résout. Ils ont donc supprimé l'héritage multiple du langage (tout comme ils ont réduit la surcharge des opérateurs). La vaste expérience C ++ des concepteurs leur a appris que l'héritage multiple ne valait tout simplement pas le mal de tête.

Razzie
la source
10
Belle comparaison. Tout à fait révélateur des processus de pensée qui sous-tendent les deux plates-formes, je pense.
CurtainDog
97

L'héritage multiple de l' implémentation est ce qui n'est pas autorisé.

Le problème est que le compilateur / runtime ne peut pas déterminer quoi faire si vous avez une classe Cowboy et une classe Artist, toutes deux avec des implémentations pour la méthode draw (), et que vous essayez ensuite de créer un nouveau type CowboyArtist. Que se passe-t-il lorsque vous appelez la méthode draw ()? Quelqu'un est-il mort dans la rue ou avez-vous une belle aquarelle?

Je crois que cela s'appelle le problème de l'héritage du double diamant.

duffymo
la source
80
Vous obtenez une belle aquarelle de quelqu'un mort dans la rue :-)
Dan F
Est-ce le seul problème? Je pense, je ne suis pas sûr, que le C ++ résout ce problème par le mot clé virtuel, est-ce vrai? Je ne suis pas bon en C ++
Abdulsattar Mohammed
2
En C ++, vous pouvez spécifier les fonctions de classe de base à appeler en dérivée ou la réimplémenter vous-même.
Dmitry Risenberg
1
Le design moderne a tendance à favoriser la composition par rapport à l'héritage dans tous les cas sauf les plus simples. L'héritage multiple n'aurait jamais compté comme un cas simple. Avec la composition, vous avez un contrôle précis sur ce que fait votre classe quand il y a des problèmes de diamants comme celui-ci ...
Bill Michell
Vous auriez simplement besoin d'une liste de priorités. Ceci est utilisé dans le système d'objets de Common Lisp, CLOS. Toutes les classes forment une héritière et la méthode appelée est déterminée à partir de là. De plus, CLOS vous permet de définir différentes règles pour qu'un CowboyArtist.draw () puisse d'abord dessiner un Cowboy puis un Artiste. Ou quoi que vous inventiez.
Sebastian Krog
19

Raison: Java est très populaire et facile à coder, en raison de sa simplicité.

Donc, quoi que les développeurs Java se sentent difficile et compliqué à comprendre pour les programmeurs, ils ont essayé de l'éviter. Un tel type de propriété est l'héritage multiple.

  1. Ils évitaient les pointeurs
  2. Ils ont évité l'héritage multiple.

Problème d'héritage multiple: problème de diamant.

Exemple :

  1. Supposons que la classe A a une méthode fun (). la classe B et la classe C dérivent de la classe A.
  2. Et les classes B et C surchargent la méthode fun ().
  3. Supposons maintenant que la classe D hérite à la fois de la classe B et de la classe C. (juste hypothèse)
  4. Créez un objet pour la classe D.
  5. D d = nouveau D ();
  6. et essayez d'accéder à d.fun (); => appellera-t-il fun () de la classe B ou fun () de la classe C?

Telle est l'ambiguïté qui existe dans le problème des diamants.

Il n'est pas impossible de résoudre ce problème, mais cela crée plus de confusion et de complexité pour le programmeur lors de sa lecture. Cela cause plus de problèmes qu'il ne tente de résoudre.

Remarque : De toute façon, vous pouvez toujours implémenter indirectement l'héritage multiple en utilisant des interfaces.

user1923551
la source
1
"Mais de toute façon, vous pouvez toujours implémenter indirectement l'héritage multiple en utilisant des interfaces." - qui oblige le programmeur à re-spécifier le corps des méthodes à chaque fois, encore et encore.
Kari le
13

Parce que Java a une philosophie de conception très différente de C ++. (Je ne vais pas discuter de C # ici.)

Lors de la conception du C ++, Stroustrup souhaitait inclure des fonctionnalités utiles, quelle que soit la manière dont elles pourraient être mal utilisées. Il est possible de se tromper avec l'héritage multiple, la surcharge d'opérateurs, les modèles et diverses autres fonctionnalités, mais il est également possible de faire de très bonnes choses avec eux.

La philosophie de conception Java est de mettre l'accent sur la sécurité dans les constructions de langage. Le résultat est qu'il y a des choses qui sont beaucoup plus délicates à faire, mais vous pouvez être beaucoup plus sûr que le code que vous regardez signifie ce que vous pensez qu'il fait.

De plus, Java était dans une large mesure une réaction de C ++ et Smalltalk, les langages OO les plus connus. Il existe de nombreux autres langages OO (Common Lisp était en fait le premier à être normalisé), avec différents systèmes OO qui gèrent mieux MI.

Sans oublier qu'il est tout à fait possible de faire du MI en Java, en utilisant les interfaces, la composition et la délégation. C'est plus explicite qu'en C ++, et donc plus maladroit à utiliser, mais vous apportera quelque chose que vous êtes plus susceptible de comprendre à première vue.

Il n'y a pas de bonne réponse ici. Il existe différentes réponses, et celle qui est la meilleure pour une situation donnée dépend des applications et des préférences individuelles.

David Thornley
la source
12

La principale (bien que ce ne soit pas la seule) raison pour laquelle les gens s'éloignent de l'IM est le soi-disant «problème du diamant» qui conduit à une ambiguïté dans votre mise en œuvre. Cet article de wikipedia en parle et explique mieux que je ne pourrais. MI peut également conduire à un code plus complexe, et de nombreux concepteurs OO affirment que vous n'avez pas besoin de MI, et si vous l'utilisez, votre modèle est probablement faux. Je ne suis pas sûr d'être d'accord avec ce dernier point, mais garder les choses simples est toujours un bon plan.

Steve
la source
8

En C ++, l'héritage multiple était un casse-tête majeur lorsqu'il était mal utilisé. Pour éviter ces problèmes de conception courants, l '«héritage» de plusieurs interfaces a été forcé à la place dans les langues modernes (java, C #).

inazaruk
la source
8

L'héritage multiple est

  • difficile à comprendre
  • difficile à déboguer (par exemple, si vous mélangez des classes de plusieurs frameworks qui ont des méthodes de nom identique en profondeur, des synergies assez inattendues peuvent se produire)
  • facile à mal utiliser
  • pas vraiment que utile
  • difficile à mettre en œuvre, surtout si vous voulez que cela soit fait correctement et efficacement

Par conséquent, il peut être considéré comme un choix judicieux de ne pas inclure l'héritage multiple dans le langage Java.

mfx
la source
3
Je ne suis pas d'accord avec tout ce qui précède, ayant travaillé avec le système d'objets Common Lisp.
David Thornley
2
Les langages dynamiques ne comptent pas ;-) Dans tout système de type Lisp, vous avez un REPL qui facilite le débogage. De plus, CLOS (si je comprends bien, je ne l'ai jamais vraiment utilisé, je ne l'ai lu qu'à ce sujet) est un système de méta-objets, avec beaucoup de flexibilité et une attitude de rouler soi-même. Mais considérons un langage compilé statiquement comme C ++, où le compilateur génère une recherche de méthode diaboliquement complexe en utilisant plusieurs vtables (éventuellement superposées): dans une telle implémentation, découvrir quelle implémentation d'une méthode a été invoquée pourrait être une tâche pas si triviale.
mfx
5

Une autre raison est que l'héritage unique rend la conversion triviale, n'émettant aucune instruction d'assembleur (autre que la vérification de la compatibilité des types si nécessaire). Si vous avez un héritage multiple, vous devez déterminer où commence un certain parent dans la classe enfant. La performance est donc certainement un avantage (mais pas le seul).

Aveugle
la source
4

Dans le passé (années 70), lorsque l'informatique était plus scientifique et moins production de masse, les programmeurs avaient le temps de réfléchir à une bonne conception et une bonne mise en œuvre et, par conséquent, les produits (programmes) étaient de haute qualité (par exemple, conception TCP / IP et mise en œuvre). De nos jours, quand tout le monde programme et que les gestionnaires changent les spécifications avant les dates limites, des problèmes subtils comme celui décrit dans le lien wikipedia du post de Steve Haigh sont difficiles à suivre; par conséquent, "l'héritage multiple" est limité par la conception du compilateur. Si vous l'aimez, vous pouvez toujours utiliser C ++ ... et avoir toute la liberté que vous voulez :)


la source
4
... y compris la liberté de se tirer une balle dans le pied, plusieurs fois;)
Mario Ortegón
4

Je prends la déclaration que "l'héritage multiple n'est pas autorisé en Java" avec une pincée de sel.

L'héritage multiple est défini lorsqu'un "Type" hérite de plusieurs "Types". Et les interfaces sont également classées comme types car elles ont un comportement. Java a donc un héritage multiple. Juste que c'est plus sûr.

Rig Veda
la source
6
Mais vous n'héritez pas d'une interface, vous l'implémentez. Les interfaces ne sont pas des classes.
Blorgbeard sort le
2
Oui, mais l'héritage n'a jamais été aussi étroitement défini, sauf dans certains livres préliminaires. Et je pense que nous devrions regarder au-delà de la grammaire de celui-ci. Ils font tous les deux la même chose et les interfaces n'ont été «inventées» que pour cette raison.
Rig Veda
Prenez l'interface Closeable: une méthode, close (). Supposons que vous vouliez étendre cela à Openable: il a une méthode open () qui définit un champ booléen isOpen sur true. Essayer d'ouvrir un Openable qui est déjà ouvert lance une Exception, tout comme essayer de fermer un Openable qui n'est pas ouvert ... Des centaines de généalogies de classes plus intéressantes pourraient vouloir adopter une telle fonctionnalité ... sans avoir à choisir de les étendre heure de la classe Ouvrable. Si Openable était simplement une interface, il ne pourrait pas fournir cette fonctionnalité. QED: les interfaces ne fournissent pas d'héritage!
mike rodent
4

Le chargement dynamique des classes rend difficile l'implémentation de l'héritage multiple.

En java, ils ont en fait évité la complexité de l'héritage multiple à la place en utilisant un héritage et une interface uniques. La complexité de l'héritage multiple est très élevée dans une situation comme expliqué ci-dessous

problème de diamant de l'héritage multiple. Nous avons deux classes B et C héritées de A. Supposons que B et C surchargent une méthode héritée et fournissent leur propre implémentation. Désormais, D hérite à la fois de B et de C faisant l'héritage multiple. D devrait hériter de cette méthode remplacée, jvm ne peut pas décider quelle méthode remplacée sera utilisée?

En C ++, les fonctions virtuelles sont utilisées pour gérer et nous devons le faire explicitement.

Cela peut être évité en utilisant des interfaces, il n'y a pas de corps de méthode. Les interfaces ne peuvent pas être instanciées - elles ne peuvent être implémentées que par des classes ou étendues par d'autres interfaces.

sujith s
la source
3

En fait, l'héritage multiple se produira à la complexité si les classes héritées ont la même fonction. c'est-à-dire que le compilateur aura une confusion que l'on doit choisir (problème de diamant). Donc, en Java, cette complexité a été supprimée et a donné une interface pour obtenir les fonctionnalités telles que l'héritage multiple. Nous pouvons utiliser l'interface

Jinu
la source
2

Java a un concept, c'est-à-dire un polymorphisme. Il existe 2 types de polymorphisme en java. Il existe une surcharge de méthode et un remplacement de méthode. Parmi eux, le remplacement de méthode se produit avec une relation de super-classe et de sous-classe. Si nous créons un objet d'une sous-classe et invoquons la méthode de la superclasse, et si la sous-classe étend plus d'une classe, quelle méthode de super-classe doit être appelée?

Ou, en appelant le constructeur de superclasse par super(), quel constructeur de superclasse sera appelé?

Ces décisions sont impossibles par les fonctionnalités actuelles de l'API Java. donc l'héritage multiple n'est pas autorisé en java.

Abhay Kokate
la source
Fondamentalement, le système de types de Java suppose que chaque instance d'objet a un type, la conversion d'un objet en supertype fonctionnera toujours et préservera les références, et la conversion d'une référence à un sous-type préservera les références si l'instance est de ce sous-type ou d'un sous-type de celui-ci. De telles hypothèses sont utiles, et je ne pense pas qu'il soit possible d'autoriser l'héritage multiple généralisé d'une manière cohérente avec elles.
supercat du
1

L'héritage multiple n'est pas autorisé directement dans Java, mais via les interfaces, il est autorisé.

Raison:

Héritage multiple: introduit plus de complexité et d'ambiguïté.

Interfaces: les interfaces sont des classes complètement abstraites en Java qui vous offrent un moyen uniforme de délimiter correctement la structure ou le fonctionnement interne de votre programme à partir de son interface accessible au public, avec pour conséquence une plus grande flexibilité et un code réutilisable ainsi qu'un meilleur contrôle. sur la façon dont vous créez et interagissez avec d’autres classes.

Plus précisément, il s'agit d'une construction spéciale en Java avec la caractéristique supplémentaire qui vous permet d'effectuer une sorte d'héritage multiple, c'est-à-dire des classes qui peuvent être remontées vers plus d'une classe.

Prenons un exemple simple.

  1. Supposons qu'il existe 2 classes de superclasses A et B avec les mêmes noms de méthode mais des fonctionnalités différentes. Grâce au code suivant avec le mot clé (extend), l'héritage multiple n'est pas possible.

       public class A                               
         {
           void display()
             {
               System.out.println("Hello 'A' ");
             }
         }
    
       public class B                               
          {
            void display()
              {
                System.out.println("Hello 'B' ");
              }
          }
    
      public class C extends A, B    // which is not possible in java
        {
          public static void main(String args[])
            {
              C object = new C();
              object.display();  // Here there is confusion,which display() to call, method from A class or B class
            }
        }
  2. Mais grâce aux interfaces, avec (implémente) l'héritage multiple de mots-clés est possible.

    interface A
        {
           // display()
        }
    
    
     interface B
        {
          //display()
        }
    
     class C implements A,B
        {
           //main()
           C object = new C();
           (A)object.display();     // call A's display
    
           (B)object.display(); //call B's display
        }
    }
Chaitra Deshpande
la source
0

Quelqu'un peut-il me dire précisément pourquoi ce n'est pas autorisé?

Vous pouvez trouver la réponse à partir de ce lien de documentation

Une des raisons pour lesquelles le langage de programmation Java ne vous permet pas d'étendre plus d'une classe est d'éviter les problèmes d'héritage multiple d'état, qui est la possibilité d'hériter des champs de plusieurs classes

Si l'héritage multiple est autorisé et lorsque vous créez un objet en instanciant cette classe, cet objet héritera des champs de toutes les super classes de la classe. Cela causera deux problèmes.

  1. Et si des méthodes ou des constructeurs de différentes super classes instanciaient le même champ?

  2. Quelle méthode ou quel constructeur aura la priorité?

Même si l'héritage multiple d'état est désormais autorisé, vous pouvez toujours implémenter

Héritage multiple de type : capacité d'une classe à implémenter plus d'une interface.

Héritage multiple de l'implémentation (via les méthodes par défaut dans les interfaces): possibilité d'hériter des définitions de méthode de plusieurs classes

Reportez-vous à cette question SE connexe pour plus d'informations:

Ambiguïté d'héritage multiple avec interface

Ravindra babu
la source
0

En C ++, une classe peut hériter (directement ou indirectement) de plusieurs classes, ce que l'on appelle l' héritage multiple .

Cependant, C # et Java limitent les classes à un seul héritage, chaque classe hérite d'une seule classe parente.

L'héritage multiple est un moyen utile de créer des classes qui combinent des aspects de deux hiérarchies de classes disparates, ce qui se produit souvent lors de l'utilisation de différents frameworks de classes dans une seule application.

Si deux frameworks définissent leurs propres classes de base pour les exceptions, par exemple, vous pouvez utiliser l'héritage multiple pour créer des classes d'exceptions qui peuvent être utilisées avec l'un ou l'autre framework.

Le problème de l'héritage multiple est qu'il peut conduire à une ambiguïté. L'exemple classique est lorsqu'une classe hérite de deux autres classes, dont chacune hérite de la même classe:

class A {
    protected:
    bool flag;
};
class B : public A {};
class C : public A {};
class D : public B, public C {
    public:
    void setFlag( bool nflag ){
        flag = nflag; // ambiguous
    }
};

Dans cet exemple, le flagmembre de données est défini par class A. Mais class Ddescend de class B et class C, qui dérivent tous deux de A, donc, en substance, deux copies de flagsont disponibles parce que deux instances de Asont dans Dla hiérarchie de classes de. Lequel souhaitez-vous définir? Le compilateur se plaindra que la référence à flagdans Dest ambiguë . Un correctif consiste à clarifier explicitement la référence:

B::flag = nflag;

Un autre correctif consiste à déclarer B et C comme virtual base classes, ce qui signifie qu'une seule copie de A peut exister dans la hiérarchie, éliminant toute ambiguïté.

D'autres complexités existent avec l'héritage multiple, telles que l'ordre dans lequel les classes de base sont initialisées lorsqu'un objet dérivé est construit, ou la façon dont les membres peuvent être masqués par inadvertance des classes dérivées. Pour éviter ces complexités, certains langages se limitent au modèle d'héritage unique plus simple.

Bien que cela simplifie considérablement l'héritage, cela limite également son utilité car seules les classes avec un ancêtre commun peuvent partager des comportements. Les interfaces atténuent quelque peu cette restriction en permettant aux classes de différentes hiérarchies d'exposer des interfaces communes même si elles ne sont pas implémentées en partageant du code.

zerocool
la source
-1

Imaginez cet exemple: j'ai une classe Shape1

Il a la CalcualteAreaméthode:

Class Shape1
{

 public void CalculateArea()

     {
       //
     }
}

Il y a une autre classe Shape2que l'on a aussi la même méthode

Class Shape2
{

 public void CalculateArea()

     {

     }
}

Maintenant, j'ai une classe enfant Circle, elle dérive à la fois de Shape1 et Shape2;

public class Circle: Shape1, Shape2
{
}

Maintenant, lorsque je crée un objet pour Circle et que j'appelle la méthode, le système ne sait pas quelle méthode de calcul de la surface doit être appelée. Les deux ont les mêmes signatures. Donc, le compilateur sera confus. C'est pourquoi les héritages multiples ne sont pas autorisés.

Mais il peut y avoir plusieurs interfaces car les interfaces n'ont pas de définition de méthode. Même les deux interfaces ont la même méthode, les deux n'ont aucune implémentation et toujours la méthode de la classe enfant sera exécutée.

Engr Muhammad Enayet Abdullah
la source
Le concepteur de langage peut donner un appel de méthode explicite comme ils le font dans Interface. Il n'y a pas de tel point! Une autre raison est que diriez-vous de la classe abstraite avec des méthodes abstraites maintenant comment considérez-vous cela?
Nomi Ali