Jame Gosling a dit
"Vous devez éviter l'héritage d'implémentation autant que possible."
et utilisez plutôt l'héritage d'interface.
Mais pourquoi? Comment pouvons-nous éviter d'hériter de la structure d'un objet en utilisant le mot-clé "extend" 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 du type "commander un livre dans une librairie?"
Réponses:
Gosling n'a pas dit d'éviter l'utilisation du mot-clé extend ... il vient de dire 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 autre chose que la création de structures d'objet), il crée un code 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 est tout aussi facile de le faire avec des interfaces, d'où la déclaration qui consiste à utiliser des interfaces plutôt que des classes lors de la conception de vos structures d'objet. Si vous utilisez les extensions, cela vous évitera de copier-coller du code d'un objet à un autre. Il serait peut-être préférable de l'utiliser avec une classe spécialisée pour gérer cette fonctionnalité et partager ce code via la composition.
Lisez à propos du principe de substitution de Liskov et comprenez-le. Chaque fois que vous êtes sur le point d'étendre une classe (ou une interface d'ailleurs), demandez-vous si vous enfreignez le principe. Si oui, vous utiliserez probablement l'héritage de manière non naturelle. C'est facile à faire et cela semble souvent être la bonne chose, mais cela rendra votre code plus difficile à maintenir, à tester et à faire évoluer.
--- Modifier Cette réponse constitue un très bon argument pour répondre à la question en termes plus généraux.
la source
Aux débuts de la programmation orientée objet, l'héritage d'implémentation était le marteau d'or de la réutilisation de code. Si vous aviez besoin d'une fonctionnalité dans une classe, il fallait hériter publiquement de cette classe (à l'époque où le C ++ était plus répandu que Java), et vous obteniez cette fonctionnalité "gratuitement".
Sauf que ce n'était pas vraiment gratuit. Le résultat était des hiérarchies de succession extrêmement compliquées et presque impossibles à comprendre, y compris des arbres de succession en forme de losange difficiles à manipuler. Cela fait partie de la raison pour laquelle les concepteurs de Java ont choisi de ne pas prendre en charge l'héritage multiple de classes.
Les conseils de Gosling (et d’autres affirmations) sont très favorables à une maladie assez grave, et il est possible que des 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 une fonctionnalité d'une autre classe, vous ferez mieux de rechercher d'autres options en premier.la source
L'héritage a acquis une réputation plutôt effrayante récemment. Une des raisons de l'éviter est conforme au principe de conception de "composition sur héritage", qui encourage fondamentalement les gens à ne pas utiliser l'héritage si ce n'est pas la vraie relation, juste pour économiser de l'écriture de code. Ce type d'héritage peut causer 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 offrent une solution plus flexible et plus facile à gérer pour les API distribuées. Ils permettent plus souvent d'apporter des modifications à une API sans altérer le code de l'utilisateur, alors que les classes d'exposition exposent souvent le code de l'utilisateur. Le meilleur exemple de ceci dans .Net est la différence d’utilisation entre List et IList.
la source
Parce que vous ne pouvez étendre qu'une classe, alors que vous pouvez implémenter plusieurs interfaces.
J'ai déjà entendu dire que l'héritage définit ce que vous êtes, mais que les interfaces définissent un rôle que vous pouvez jouer.
Les interfaces permettent également une meilleure utilisation du polymorphisme.
Cela pourrait faire partie de la raison.
la source
On m'a appris cela aussi et je préfère les interfaces dans la mesure du possible (bien sûr, j'utilise toujours l'héritage là où c'est logique).
Une des choses que je pense est de dissocier votre code des implémentations spécifiques. Supposons que je possède une classe appelée ConsoleWriter qui est transmise à une méthode pour écrire quelque chose et l’écrit sur la console. Maintenant, disons que je veux passer à l’impression dans une fenêtre graphique. Eh bien, je dois maintenant modifier la méthode ou en écrire une nouvelle qui prend GUIWriter en tant que paramètre. Si je commençais par définir une interface IWriter et que la méthode prenne un IWriter, je pourrais commencer par ConsoleWriter (qui implémenterait l'interface IWriter), puis écrire une nouvelle classe appelée GUIWriter (qui implémente également l'interface IWriter), puis I aurait juste à changer la classe en cours de passage.
Une autre chose (ce qui est vrai pour C #, pas sûr de Java) est que vous ne pouvez étendre qu'une classe mais implémenter de nombreuses interfaces. Disons que j'ai eu des cours nommés Teacher, MathTeacher et HistoryTeacher. Maintenant, MathTeacher et HistoryTeacher s’étendent de Teacher, mais que se passe-t-il si nous voulons un cours qui représente à la fois un MathTeacher et un HistoryTeacher. Cela peut devenir assez compliqué d'essayer d'hériter de plusieurs classes alors que vous ne pouvez le faire que l'un après l'autre (il y a moyen, mais ils ne sont pas exactement optimaux). Avec les interfaces, vous pouvez avoir 2 interfaces appelées IMathTeacher et IHistoryTeacher, puis une classe qui s'étend de Teacher et implémente ces 2 interfaces.
L’inconvénient de l’utilisation des interfaces est que parfois je vois des personnes dupliquer du code (puisque vous devez créer l’implémentation pour chaque classe, l’implémentation de l’interface), mais il existe une solution propre à ce problème, par exemple l’utilisation de choses comme les délégués quel est l'équivalent Java de cela).
La principale raison d'utiliser les interfaces sur l'héritage 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.
la source
Si vous héritez d'une classe, vous prenez une dépendance non seulement de cette classe, mais vous devez également respecter les contrats implicites créés par les clients de cette classe. Oncle Bob donne un excellent exemple de la manière dont il est facile de violer de manière subtile le principe de substitution de Liskov en utilisant le dilemme de base de Square . Les interfaces, puisqu'elles n'ont pas d'implémentation, n'ont pas non plus de contrat caché.
la source