Ok. Ça va être long alors je coupe un peu. J'espère ne rien oublier...
Pourquoi ?
Je suppose que tu veux savoir pourquoi héritage structurel et héritage comportemental sont différents (si c'est « pourquoi l'héritage multiple pose problème », j'en reparle plus bas).
L'héritage de structure, c'est lorsque l'on hérite des composants, des attributs, au niveau du modèle (donc lorsque l'on est proche du monde réel). Conceptuellement, il est très rare (même dans les cas d'école) d'avoir besoin d'hériter de plusieurs structures.
P.ex. un Point2DColoré hérite d'un Point2D sa structure (en gros, x et y). Mais un Rectangle ne devrait pas hériter d'un Point2D : un rectangle peut être défini de bien des manières (= 2 points, = 1 point + largeur + hauteur, etc.) mais on ne peut pas dire « un rectangle est un point ».
L'héritage de comportement, c'est lorsque l'on hérite de la façon de se comporter, des services donnés.
P.ex. un Point2DTranslatable promet l'interface Translatable, tout comme un RectangleTranslatable.
La différence entre classe et interface tient aussi de la différence entre type et classe.
P.ex., une interface qui pour moi est simple et qui n'est incontestablement pas une classe, c'est Comparable. Deux entiers sont comparables entre eux, deux voitures peuvent être comparables entre elles (p.ex. suivant un ordre de préférence qu'aurait le programmeur), mais un entier n'est pas une voiture et une voiture n'est pas un entier. Par contre, grâce à ce type Comparable, je peux écrire une fonction de tri d'un tableau d'objets de ce type et je pourrais utiliser cette fonction sur un tableau de voitures ou un tableau d'entiers sans avoir à redéfinir la fonction et, ce qui est important1, avec un typage fort.
1 : c'est important ici car si le typage fort n'est pas dans le langage, alors les concepts ne sont plus les mêmes et la différence type / classe ne sert plus à rien.
Pour éclairer un peu la différence type / classe, une variable a un type (en Java, C++...), et un objet a une classe et des types.
Quand je me heurte concrètement à ce problème[...] l'API.
Oui. P.ex. si je propose une interface Coloré pour mes points et rectangles : se pose le problème de l'attribut couleur qui a de fortes chances de se retrouver dans toutes les classes promettant le service.
C'est parce que :
- soit il fallait prévoir dès le début qu'un objet géométrique pouvait avoir une couleur (pas deux classes : Point2D et Point2DColoré mais seulement ObjetGéoColoré et les autres qui en héritent) ;
- soit il faut utiliser un autre mécanisme, absent des primitives Java (mais que l'on peut simuler par délégation), les Mixin ou les Traits.
C'est a dire ? L'héritage en diamant me semble poser un problème dans l'implémentation du langage, pas pour le programmeur, je me trompe ?
Oui. Le classique cas: classe A avec attribut a, classe B et C qui héritent de A et classe D qui hérite de B et C. L'attribut a est hérité deux fois par D. Comment gérer ça ? Qu'est-ce que cela peut bien signifier ?
Parfois, le programmeur voudrait deux a, parfois il n'en veut qu'un. C'est donc aussi un problème dans la tête du programmeur.
D'autre part je continue à ne pas apprécier la délégation qui impose du code "dupliqué" au kilomètre.
Des automatismes sont maintenant utilisés pour simplement noté qu'un attribut est en fait délégué et ses fonctions sont « proxysées » automatiquement.
C'est un exemple rapidement fait [...]
C'est un peu le problème avec tous les exemples que l'on me sort pour dire que l'héritage multiple est plus utile que néfaste ;o)
D'ailleurs quelle est vraiment la différence ave le fait d'hériter d'une classe et d'implémenter une ou plusieurs interfaces ?
Aucune et plein.
Une interface est une classe abstraite, donc c'est une classe, donc on en hérite.
Mais cela correspond à un héritage de comportement, pas de structure. (Voir plus haut type / classe...)
Je vais te donner un autre cas concr[e]t [...] Est-ce une mauvaise conception ? A cause du manque de l'héritage multiple, je ne sais pas comment faire ça élégamment en Java.
Oui, c'est une mauvaise conception : le modèle de calcul (= les classes qui servent à représenter tes données géométriques) doit être découplé du modèle de dessin (UI).
C'est le MVC.
Ok. Tu vas me dire que tu dois dupliquer encore ton arbre d'héritage mais c'est à toi de choisir :
- ou tu sépares proprement application et interface (c'est-à-dire que tu fais un modèle pour le calcul, un modèle-proxy pour donner à l'UI et une UI (avec ses objets à elle : widgets de toutes sortes)) ;
- ou tu ne fais qu'un modèle qui mélange à la fois les structures et les services pour le calcul et pour l'UI.
Uniquement parce que dans mon exemple tu suggères de remplacer des classes par des instances ?
Tu admettras quand même que les concepts d'instance et de classe sont suffisamment distincts pour faire que, pour un même modèle, seulement un seul peut s'appliquer (c.-à-d. que tu n'as pas le choix entre l'un ou l'autre : il y en a un qui s'applique et l'autre qui ne veut rien dire).
Est-ce que tu peux m'expliquer pourquoi il est conceptuellement correct d'avoir un héritage simple et de l'implémentation d'interface(s), mais pas correct d'avoir un héritage multiple ? (en fait cette question se rapproche beaucoup de ma question du paragraphe précédent)
Conceptuellement, il y a un problème dans le cas du diamant : que veut dire hériter de deux fois la même structure ?
Conceptuellement, l'héritage multiple englobe aussi les Mixin. (Ruby les différencie en utilisant des modules, j'aurais peut-être préféré un mot-clef mixin.)
Donc, je ne dis pas que ce n'est pas correct, mais, comme on est parfois obligé lorsque l'on enseigne des concepts complexes, on exagère un peu les mises en garde (parce que tout le monde se croit assez malin pour les transgresser) en en faisant des interdictions (puisque, de toute façon, on peut s'en passer).
Donc, comme la plupart des problèmes de l'héritage multiple (tous ?) se posent pour l'héritage de structure multiple, une solution pour les éviter est de l'empêcher. Par contre, l'héritage multiple de services ne pose pas de problème (si deux services ont le même nom, ils doivent représenter la même chose, sinon il y a un problème de nommage dans le modèle).
Enfin, pour mettre en perspective la pensée que l'héritage multiple est un symptôme de mauvaise conception [...]
Je pense en fait que l'on est en train d'arriver à des concepts plus précis.
Au départ, il n'y a que l'objet, tout est objet, blabla... Mais on s'est vite rendu compte qu'il y avait plein de concepts différents et on a pas encore fini de les différencier correctement. On différencie maintenant héritage de structure et de services, on parle d'aspects, de mixins... Il faut encore poser tout ça pour que ça ressemble à quelque chose.
[1] je rapproche [...]
1. Le GC de Java fonctionne aussi avec compteur de références. Je pense que tu voulais plutôt dire que le nettoyage est, dans un cas, seulement fait lorsqu'il y a besoin de place, et dans l'autre cas, dès que la référence arrive à zéro.
2. Pour les histoires de propreté avec les E/S etc., il est à mon avis bien plus propre de le faire explicitement (et même dans le finally d'un try pour être bien sûr que c'est fait).
[^] # Re: Mes deux centimes ...
Posté par Sylvain Sauvage . En réponse au journal Repenser les langages et le développement logiciel. Évalué à 7.
Je suppose que tu veux savoir pourquoi héritage structurel et héritage comportemental sont différents (si c'est « pourquoi l'héritage multiple pose problème », j'en reparle plus bas).
L'héritage de structure, c'est lorsque l'on hérite des composants, des attributs, au niveau du modèle (donc lorsque l'on est proche du monde réel). Conceptuellement, il est très rare (même dans les cas d'école) d'avoir besoin d'hériter de plusieurs structures.
P.ex. un Point2DColoré hérite d'un Point2D sa structure (en gros, x et y). Mais un Rectangle ne devrait pas hériter d'un Point2D : un rectangle peut être défini de bien des manières (= 2 points, = 1 point + largeur + hauteur, etc.) mais on ne peut pas dire « un rectangle est un point ».
L'héritage de comportement, c'est lorsque l'on hérite de la façon de se comporter, des services donnés.
P.ex. un Point2DTranslatable promet l'interface Translatable, tout comme un RectangleTranslatable.
La différence entre classe et interface tient aussi de la différence entre type et classe.
P.ex., une interface qui pour moi est simple et qui n'est incontestablement pas une classe, c'est Comparable. Deux entiers sont comparables entre eux, deux voitures peuvent être comparables entre elles (p.ex. suivant un ordre de préférence qu'aurait le programmeur), mais un entier n'est pas une voiture et une voiture n'est pas un entier. Par contre, grâce à ce type Comparable, je peux écrire une fonction de tri d'un tableau d'objets de ce type et je pourrais utiliser cette fonction sur un tableau de voitures ou un tableau d'entiers sans avoir à redéfinir la fonction et, ce qui est important1, avec un typage fort.
1 : c'est important ici car si le typage fort n'est pas dans le langage, alors les concepts ne sont plus les mêmes et la différence type / classe ne sert plus à rien.
Pour éclairer un peu la différence type / classe, une variable a un type (en Java, C++...), et un objet a une classe et des types.
Oui. P.ex. si je propose une interface Coloré pour mes points et rectangles : se pose le problème de l'attribut couleur qui a de fortes chances de se retrouver dans toutes les classes promettant le service.
C'est parce que :
- soit il fallait prévoir dès le début qu'un objet géométrique pouvait avoir une couleur (pas deux classes : Point2D et Point2DColoré mais seulement ObjetGéoColoré et les autres qui en héritent) ;
- soit il faut utiliser un autre mécanisme, absent des primitives Java (mais que l'on peut simuler par délégation), les Mixin ou les Traits.
Oui. Le classique cas: classe A avec attribut a, classe B et C qui héritent de A et classe D qui hérite de B et C. L'attribut a est hérité deux fois par D. Comment gérer ça ? Qu'est-ce que cela peut bien signifier ?
Parfois, le programmeur voudrait deux a, parfois il n'en veut qu'un. C'est donc aussi un problème dans la tête du programmeur.
Des automatismes sont maintenant utilisés pour simplement noté qu'un attribut est en fait délégué et ses fonctions sont « proxysées » automatiquement.
C'est un peu le problème avec tous les exemples que l'on me sort pour dire que l'héritage multiple est plus utile que néfaste ;o)
Aucune et plein.
Une interface est une classe abstraite, donc c'est une classe, donc on en hérite.
Mais cela correspond à un héritage de comportement, pas de structure. (Voir plus haut type / classe...)
Oui, c'est une mauvaise conception : le modèle de calcul (= les classes qui servent à représenter tes données géométriques) doit être découplé du modèle de dessin (UI).
C'est le MVC.
Ok. Tu vas me dire que tu dois dupliquer encore ton arbre d'héritage mais c'est à toi de choisir :
- ou tu sépares proprement application et interface (c'est-à-dire que tu fais un modèle pour le calcul, un modèle-proxy pour donner à l'UI et une UI (avec ses objets à elle : widgets de toutes sortes)) ;
- ou tu ne fais qu'un modèle qui mélange à la fois les structures et les services pour le calcul et pour l'UI.
Tu admettras quand même que les concepts d'instance et de classe sont suffisamment distincts pour faire que, pour un même modèle, seulement un seul peut s'appliquer (c.-à-d. que tu n'as pas le choix entre l'un ou l'autre : il y en a un qui s'applique et l'autre qui ne veut rien dire).
Conceptuellement, il y a un problème dans le cas du diamant : que veut dire hériter de deux fois la même structure ?
Conceptuellement, l'héritage multiple englobe aussi les Mixin. (Ruby les différencie en utilisant des modules, j'aurais peut-être préféré un mot-clef mixin.)
Donc, je ne dis pas que ce n'est pas correct, mais, comme on est parfois obligé lorsque l'on enseigne des concepts complexes, on exagère un peu les mises en garde (parce que tout le monde se croit assez malin pour les transgresser) en en faisant des interdictions (puisque, de toute façon, on peut s'en passer).
Donc, comme la plupart des problèmes de l'héritage multiple (tous ?) se posent pour l'héritage de structure multiple, une solution pour les éviter est de l'empêcher. Par contre, l'héritage multiple de services ne pose pas de problème (si deux services ont le même nom, ils doivent représenter la même chose, sinon il y a un problème de nommage dans le modèle).
Je pense en fait que l'on est en train d'arriver à des concepts plus précis.
Au départ, il n'y a que l'objet, tout est objet, blabla... Mais on s'est vite rendu compte qu'il y avait plein de concepts différents et on a pas encore fini de les différencier correctement. On différencie maintenant héritage de structure et de services, on parle d'aspects, de mixins... Il faut encore poser tout ça pour que ça ressemble à quelque chose.
1. Le GC de Java fonctionne aussi avec compteur de références. Je pense que tu voulais plutôt dire que le nettoyage est, dans un cas, seulement fait lorsqu'il y a besoin de place, et dans l'autre cas, dès que la référence arrive à zéro.
2. Pour les histoires de propreté avec les E/S etc., il est à mon avis bien plus propre de le faire explicitement (et même dans le finally d'un try pour être bien sûr que c'est fait).