Vous venez peut-être de remarquer que des gens l'ont dit ces derniers mois, mais les bons programmeurs le savent depuis plus longtemps. Je le dis certainement, le cas échéant, depuis environ dix ans.
L'idée du concept est qu'il y a une lourde charge conceptuelle sur l'héritage. Lorsque vous utilisez l'héritage, chaque appel de méthode comporte une répartition implicite. Si vous avez des arbres d'héritage profonds, ou des envois multiples, ou (pire encore) les deux, déterminer à quel endroit la méthode particulière sera envoyée dans un appel donné peut devenir un PITA royal. Cela rend le raisonnement correct du code plus complexe et rend le débogage plus difficile.
Laissez-moi vous donner un exemple simple à illustrer. Supposons que, profondément dans un arbre d'héritage, quelqu'un ait nommé une méthode foo
. Ensuite, quelqu'un d'autre arrive et ajoute foo
au sommet de l'arbre, mais fait quelque chose de différent. (Ce cas est plus commun avec l'héritage multiple.) Maintenant, cette personne travaillant dans la classe racine a cassé la classe enfant obscure et ne s'en rend probablement pas compte. Vous pourriez avoir une couverture de 100% avec les tests unitaires et ne pas remarquer cette rupture car la personne située en haut ne penserait pas à tester la classe enfant, et les tests pour la classe enfant ne songent pas à tester les nouvelles méthodes créées en haut. . (Certes, il existe des moyens d'écrire des tests unitaires pour résoudre ce problème, mais il existe également des cas dans lesquels vous ne pouvez pas écrire facilement des tests de cette façon.)
En revanche, lorsque vous utilisez la composition, à chaque appel, le message auquel vous envoyez l'appel est généralement plus clair. (OK, si vous utilisez l'inversion de contrôle, par exemple avec l'injection de dépendance, il peut être problématique de savoir où l'appel va aller. Mais en général, c'est plus simple à comprendre.) Cela facilite le raisonnement. En prime, la composition a pour résultat que les méthodes sont séparées les unes des autres. L'exemple ci-dessus ne devrait pas s'y produire car la classe enfant passerait à un composant obscur et il n'y a jamais de question de savoir si l'appel à foo
était destiné au composant obscur ou à l'objet principal.
Maintenant, vous avez absolument raison de dire que l'héritage et la composition sont deux outils très différents qui servent deux types de choses différentes. Bien sûr, l'héritage entraîne une surcharge conceptuelle, mais lorsqu'il est le bon outil pour le travail, il entraîne moins de surcharge conceptuelle que d'essayer de ne pas l'utiliser et de faire à la main ce qu'il fait pour vous. Personne qui sait ce qu'ils font ne dirait qu'il ne faut jamais utiliser l'héritage. Mais assurez-vous que c'est la bonne chose à faire.
Malheureusement, de nombreux développeurs en apprennent davantage sur les logiciels orientés objet, sur l'héritage, puis utilisent leur nouvel axe aussi souvent que possible. Ce qui signifie qu'ils essaient d'utiliser l'héritage où la composition était le bon outil. Espérons qu'ils apprendront mieux avec le temps, mais souvent cela ne se produit qu'après quelques membres enlevés, etc. Leur dire dès le départ que c'est une mauvaise idée a tendance à accélérer le processus d'apprentissage et à réduire le nombre de blessures.