Jame Gosling a dit
"Vous devez éviter l'héritage d'implémentation dans la mesure du possible."
et à la place, utilisez l'héritage d'interface.
Mais pourquoi? Comment éviter d'hériter de la structure d'un objet en utilisant le mot-clé "étend", et en même temps rendre notre code orienté objet?
Quelqu'un pourrait-il donner un exemple orienté objet illustrant ce concept dans un scénario comme "commander un livre dans une librairie?
Gosling n'a pas dit d'éviter l'utilisation du mot-clé extend ... il a simplement dit de ne pas utiliser l'héritage comme moyen de réutiliser le code.
L'héritage n'est pas mauvais en soi et est un outil très puissant (essentiel) à utiliser pour créer des structures OO. Cependant, lorsqu'il n'est pas utilisé correctement (c'est-à-dire lorsqu'il est utilisé pour quelque chose sinon que la création de structures d'objets), il crée du code qui est très étroitement couplé et très difficile à maintenir.
Étant donné que l'héritage doit être utilisé pour créer des structures polymorphes, il peut être fait tout aussi facilement avec des interfaces, d'où l'instruction d'utiliser des interfaces plutôt que des classes lors de la conception de vos structures d'objet. Si vous vous retrouvez à utiliser des extensions pour éviter de copier-coller du code d'un objet à un autre, vous l'utilisez peut-être mal et il serait préférable de disposer d'une classe spécialisée pour gérer cette fonctionnalité et partager ce code via la composition à la place.
Lisez à propos du Liskov Substitution Principle et comprenez-le. Chaque fois que vous êtes sur le point d'étendre une classe (ou une interface d'ailleurs), demandez-vous si vous violez le principe, si oui, alors vous êtes très probablement (ab) utilisant l'héritage de manière non naturelle. C'est facile à faire et semble souvent la bonne chose, mais cela rendra votre code plus difficile à maintenir, à tester et à faire évoluer.
--- Modifier Cette réponse fait un assez bon argument pour répondre à la question en termes plus généraux.
Au début de la programmation orientée objet, l'héritage de l'implémentation était le marteau d'or de la réutilisation du code. S'il y avait un élément de fonctionnalité dans une classe dont vous aviez besoin, la chose à faire était d'hériter publiquement de cette classe (c'était l'époque où C++ était plus répandu que Java), et vous avez obtenu cette fonctionnalité "gratuitement".
Sauf que ce n'était pas vraiment gratuit. Le résultat a été des hiérarchies d'héritage extrêmement alambiquées qui étaient presque impossibles à comprendre, y compris des arbres d'héritage en forme de losange qui étaient difficiles à gérer. Cela fait partie des raisons pour lesquelles les concepteurs de Java ont choisi de ne pas prendre en charge l'héritage multiple des classes.
Les conseils de Gosling (et d'autres déclarations) comme s'il s'agissait d'un médicament puissant pour une maladie assez grave, et il est possible que certains bébés soient jetés avec l'eau du bain. Il n'y a rien de mal à utiliser l'héritage pour modéliser une relation entre les classes qui est vraiment is-a
. Mais si vous cherchez simplement à utiliser un élément de fonctionnalité d'une autre classe, vous feriez mieux d'étudier d'abord d'autres options.
L'héritage a récemment acquis une réputation assez effrayante. Une raison de l'éviter va de pair avec le principe de conception de "composition sur héritage", qui encourage essentiellement les gens à ne pas utiliser l'héritage là où ce n'est pas la vraie relation, juste pour économiser un peu d'écriture de code. Ce type d'héritage peut entraîner des bogues difficiles à trouver et à corriger. La raison pour laquelle votre ami a probablement suggéré d'utiliser une interface à la place est que les interfaces fournissent une solution plus flexible et plus facile à gérer aux API distribuées. Ils permettent le plus souvent d'apporter des modifications à une API sans casser le code de l'utilisateur, où l'exposition des classes brise souvent le code de l'utilisateur. Le meilleur exemple de cela dans .Net est la différence d'utilisation entre List et IList.
Parce que vous ne pouvez étendre qu'une seule classe, alors que vous pouvez implémenter de nombreuses interfaces.
J'ai entendu une fois que l'héritage définit ce que vous êtes, mais les interfaces définissent un rôle que vous pouvez jouer.
Les interfaces permettent également une meilleure utilisation du polymorphisme.
Cela pourrait être une partie de la raison.
On m'a appris cela aussi et je préfère les interfaces lorsque cela est possible (bien sûr, j'utilise toujours l'héritage là où c'est logique).
Je pense qu'une chose est de dissocier votre code des implémentations spécifiques. Disons que j'ai une classe appelée ConsoleWriter et qui est passée dans une méthode pour écrire quelque chose et il l'écrit sur la console. Supposons maintenant que je souhaite passer à l'impression dans une fenêtre GUI. Eh bien maintenant, je dois modifier la méthode ou en écrire une nouvelle qui prend GUIWriter comme paramètre. Si je commençais par définir une interface IWriter et que la méthode prenait un IWriter, je pouvais commencer par ConsoleWriter (qui implémenterait l'interface IWriter), puis écrire plus tard une nouvelle classe appelée GUIWriter (qui implémente également l'interface IWriter), puis je aurait juste à changer la classe en cours de réussite.
Une autre chose (ce qui est vrai pour C #, pas sûr de Java) est que vous ne pouvez étendre qu'une seule classe mais implémenter de nombreuses interfaces. Disons que j'ai eu un cours nommé Teacher, MathTeacher et HistoryTeacher. Maintenant MathTeacher et HistoryTeacher s'étendent de Teacher mais que faire si nous voulons une classe qui représente quelqu'un qui est à la fois MathTeacher et HistoryTeacher. Cela peut devenir assez compliqué lorsque vous essayez d'hériter de plusieurs classes lorsque vous ne pouvez le faire qu'une à la fois (il existe un moyen mais elles ne sont pas exactement optimales). Avec les interfaces, vous pouvez avoir 2 interfaces appelées IMathTeacher et IHistoryTeacher, puis avoir une classe qui s'étend de Teacher et implémente ces 2 interfaces.
Un inconvénient avec l'utilisation des interfaces est que parfois je vois des gens dupliquer du code (puisque vous devez créer l'implémentation pour chaque classe l'implémenter l'interface) mais il existe une solution propre à ce problème, par exemple l'utilisation de choses comme les délégués (pas sûr quel est le Java équivalent à cela).
La principale raison d'utiliser des interfaces sur les héritages est le découplage du code d'implémentation, mais ne pensez pas que l'héritage est mauvais car il est toujours très utile.
Si vous héritez d'une classe, vous prenez une dépendance non seulement sur cette classe, mais vous devez également respecter les contrats implicites créés par les clients de cette classe. Oncle Bob fournit un excellent exemple de la façon dont il est facile de violer subtilement le dilemme Principe de Substitution Liskov en utilisant le dilemme de base Square étend Rectangle . Les interfaces, puisqu'elles n'ont aucune implémentation, n'ont pas non plus de contrats cachés.