Et, à ce moment, l'on s'était rendu compte que l'héritage multiple posait beaucoup de problèmes et qu'il fallait faire une différence entre l'héritage de structure (classes) et l'héritage de comportements (interfaces).
Pourquoi ?
Quand je me heurte concrètement à ce problème, les interfaces ne sont pas une solution pratique parce que je veux attacher des données ou du code en plus de l'API.
On s'est aussi rendu compte que la majorité des problèmes de l'héritage multiple sont dus à l'héritage multiple de structures (problèmes de l'héritage en diamant, p.ex.).
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 ?
On s'est aussi rendu compte que les autres cas d'héritage multiple sont relativement rares (comparativement aux ennuis) et facilement contournables par délégation.
D'une part je continue à penser que ça m'arrive bien plus souvent que le manque de multiméthodes, même si je suis d'accord que le besoin ne s'en fait pas sentir tous les jours[1]. D'autre part je continue à ne pas apprécier la délégation qui impose du code "dupliqué" au kilomètre.
tu fais plusieurs choix discutables, p.ex. que Laguna est une classe et que Renaut est une classe. Voiture est bien une classe, mais Renault est une instance de la classe Constructeur et Laguna une instance de la classe Modèle. Voiture aura donc deux attributs : constructeur et modèle.
C'est un exemple rapidement fait pour montrer qu'on peut avoir besoin de d'hériter de deux classes distinctes. 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 ? Conceptuellement, on "suit", on "adhère" à différents "traits", "comportements", la seule différence est qu'on partage aussi des données ou du code dans un cas et pas dans l'autre. Je considère donc une interface comme une classe "réduite" mais je serais content qu'on me convainque du contraire ?
Je vais te donner un autre cas concrêt qu'il m'est arrivé, et dis-moi ce que tu penses de la conception.
Nous avons un ensemble de formes géométriques : Ligne, Rectangle, Polygone. On utilise une classe pour chacun, ce qui permet de stocker des données comme les deux points d'une Ligne ou les quatre points d'un Rectangle par exemple. On les manipule dans une application de calcul scientifique pour générer des espaces discrets.
D'autre part, nous avons un éditeur graphique pour dessiner des formes géométriques. Chaque forme géométrique devra donc implémenter certains comportements supplémentaires, comme se dessiner dans un espace graphique, mais elles ont aussi toutes en commun de posséder une couleur. Cette couleur est une donnée, et on veut rajouter des accesseurs pour la manipuler. On veut aussi rajouter du code (une méthode) pour marquer visuellement que cette forme est sélectionnée (la couleur passera à une couleur fixée, le gris en l'occurrence). Cela a été implémenté (en C++) par une classe mère GtkForme, chaque forme géométrique héritant ensuite de cette classe pour obtenir la couleur, et de sa classe de forme géométrique de base pour hériter de ses coordonnées.
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.
Alors tu me répondras : c'est de la délégation, c'est plus joli avec de l'héritage. Mais l'héritage n'a rien à voir dans ces relations. Le fait que tu puisses remplacer de l'utilisation (composition/aggrégation) par de l'héritage et que cela soit plus court à écrire n'est pas une raison pour le faire. Conceptuellement, ça ne tient pas.
Uniquement parce que dans mon exemple tu suggères de remplacer des classes par des instances ? 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)
Enfin, pour mettre en perspective la pensée que l'héritage multiple est un symptôme de mauvaise conception, je citerais le fameux Craig R. McClanahan (monsieur struts, un des monsieur tomcat) dans la javadoc de org.apache.catalina.core.ApplicationHttpRequest (source de tomcat donc ) :
* WARNING: Due to Java's lack of support for multiple
* inheritance, all of the logic in ApplicationRequest is
* duplicated in ApplicationHttpRequest. Make sure that you
* keep these two classes in synchronization when making changes!
[1] je rapproche d'ailleurs ce problème du manque de destructeur dans les GC dits "modernes" ou "vrais" (java, ruby) avec mark & sweep, par rapport aux GC dits "faux" (perl, python) fonctionnant par compteur de référence ; concrètement il est assez rare d'avoir besoin de destructeurs vrais (c'est à dire qui sont vraiment appelés quand l'objet est déscopé), mais quand on en a besoin c'est très chiant de ne pas en avoir ; surtout utile pour les IOs, et les gens de jakarta-httpclient (pour java) sont obligés d'imposer un HttpMethod#releaseConnection bien crade et bien chiant à cause de ça.
[^] # Re: Mes deux centimes ...
Posté par gc . En réponse au journal Repenser les langages et le développement logiciel. Évalué à 4.
Pourquoi ?
Quand je me heurte concrètement à ce problème, les interfaces ne sont pas une solution pratique parce que je veux attacher des données ou du code en plus de l'API.
On s'est aussi rendu compte que la majorité des problèmes de l'héritage multiple sont dus à l'héritage multiple de structures (problèmes de l'héritage en diamant, p.ex.).
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 ?
On s'est aussi rendu compte que les autres cas d'héritage multiple sont relativement rares (comparativement aux ennuis) et facilement contournables par délégation.
D'une part je continue à penser que ça m'arrive bien plus souvent que le manque de multiméthodes, même si je suis d'accord que le besoin ne s'en fait pas sentir tous les jours[1]. D'autre part je continue à ne pas apprécier la délégation qui impose du code "dupliqué" au kilomètre.
tu fais plusieurs choix discutables, p.ex. que Laguna est une classe et que Renaut est une classe. Voiture est bien une classe, mais Renault est une instance de la classe Constructeur et Laguna une instance de la classe Modèle. Voiture aura donc deux attributs : constructeur et modèle.
C'est un exemple rapidement fait pour montrer qu'on peut avoir besoin de d'hériter de deux classes distinctes. 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 ? Conceptuellement, on "suit", on "adhère" à différents "traits", "comportements", la seule différence est qu'on partage aussi des données ou du code dans un cas et pas dans l'autre. Je considère donc une interface comme une classe "réduite" mais je serais content qu'on me convainque du contraire ?
Je vais te donner un autre cas concrêt qu'il m'est arrivé, et dis-moi ce que tu penses de la conception.
Nous avons un ensemble de formes géométriques : Ligne, Rectangle, Polygone. On utilise une classe pour chacun, ce qui permet de stocker des données comme les deux points d'une Ligne ou les quatre points d'un Rectangle par exemple. On les manipule dans une application de calcul scientifique pour générer des espaces discrets.
D'autre part, nous avons un éditeur graphique pour dessiner des formes géométriques. Chaque forme géométrique devra donc implémenter certains comportements supplémentaires, comme se dessiner dans un espace graphique, mais elles ont aussi toutes en commun de posséder une couleur. Cette couleur est une donnée, et on veut rajouter des accesseurs pour la manipuler. On veut aussi rajouter du code (une méthode) pour marquer visuellement que cette forme est sélectionnée (la couleur passera à une couleur fixée, le gris en l'occurrence). Cela a été implémenté (en C++) par une classe mère GtkForme, chaque forme géométrique héritant ensuite de cette classe pour obtenir la couleur, et de sa classe de forme géométrique de base pour hériter de ses coordonnées.
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.
Alors tu me répondras : c'est de la délégation, c'est plus joli avec de l'héritage. Mais l'héritage n'a rien à voir dans ces relations. Le fait que tu puisses remplacer de l'utilisation (composition/aggrégation) par de l'héritage et que cela soit plus court à écrire n'est pas une raison pour le faire. Conceptuellement, ça ne tient pas.
Uniquement parce que dans mon exemple tu suggères de remplacer des classes par des instances ? 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)
Enfin, pour mettre en perspective la pensée que l'héritage multiple est un symptôme de mauvaise conception, je citerais le fameux Craig R. McClanahan (monsieur struts, un des monsieur tomcat) dans la javadoc de org.apache.catalina.core.ApplicationHttpRequest (source de tomcat donc ) :
* WARNING: Due to Java's lack of support for multiple
* inheritance, all of the logic in ApplicationRequest is
* duplicated in ApplicationHttpRequest. Make sure that you
* keep these two classes in synchronization when making changes!
[1] je rapproche d'ailleurs ce problème du manque de destructeur dans les GC dits "modernes" ou "vrais" (java, ruby) avec mark & sweep, par rapport aux GC dits "faux" (perl, python) fonctionnant par compteur de référence ; concrètement il est assez rare d'avoir besoin de destructeurs vrais (c'est à dire qui sont vraiment appelés quand l'objet est déscopé), mais quand on en a besoin c'est très chiant de ne pas en avoir ; surtout utile pour les IOs, et les gens de jakarta-httpclient (pour java) sont obligés d'imposer un HttpMethod#releaseConnection bien crade et bien chiant à cause de ça.