33 votes

Quand utiliser des interfaces ou des classes abstraites? Quand utiliser les deux?

Alors que certaines directives indiquent que vous devriez utiliser une interface lorsque vous souhaitez définir un contrat pour une classe où l'héritage n'est pas clair (IDomesticated) et l'héritage lorsque la classe est une extension d'une autre (Chat : Mammifère, Serpent : Reptile), il y a des cas où (à mon avis) ces directives entrent dans une zone grise.

Par exemple, disons que mon implémentation était Chat : Animal de compagnie. Animal de compagnie est une classe abstraite. Faut-il l'étendre à Chat : Mammifère, IDomesticatedMammifère est une classe abstraite et IDomesticated est une interface? Ou suis-je en conflit avec les principes KISS/YAGNI (même si je ne suis pas sûr s'il y aura une classe Loup à l'avenir, qui ne pourrait pas hériter de Animal de compagnie)?

En m'éloignant des Chats métaphoriques et des Animal de compagnie, disons que j'ai des classes qui représentent des sources de données entrantes. Elles doivent toutes implémenter la même base de quelque manière. Je pourrais mettre en œuvre du code générique dans une classe abstraite Source et en hériter. Je pourrais aussi simplement créer une interface ISource (ce qui me semble plus "juste") et réimplémenter le code générique dans chaque classe (ce qui est moins intuitif). Enfin, je pourrais "avoir le beurre et l'argent du beurre" en créant à la fois la classe abstraite et l'interface. Quel est le meilleur choix?

Ces deux cas soulèvent des points pour n'utiliser qu'une classe abstraite, qu'une interface et utiliser à la fois une classe abstraite et une interface. Ces choix sont-ils tous valides, ou existe-t-il des "règles" pour savoir quand utiliser l'un plutôt que l'autre?


J'aimerais préciser que par "utiliser à la fois une classe abstraite et une interface", cela inclut le cas où elles représentent essentiellement la même chose (Source et ISource

Il convient également de noter que cette question concerne principalement les langues qui ne prennent pas en charge l'héritage multiple (comme .NET et Java).

36voto

Mark Seemann Points 102767

En tant que première règle générale, je préfère les classes abstraites aux interfaces, basé sur les directives de conception .NET. Le raisonnement s'applique beaucoup plus largement que .NET, mais il est mieux expliqué dans le livre Guide de conception du framework.

La principale raison derrière la préférence pour les classes de base abstraites est la versioning, car vous pouvez toujours ajouter un nouveau membre virtuel à une classe de base abstraite sans rompre avec les clients existants. Ce n'est pas possible avec les interfaces.

Il y a des scénarios où une interface est toujours le choix correct (particulièrement lorsque vous ne vous souciez pas de la versioning), mais être conscient des avantages et des inconvénients vous permet de prendre la décision correcte.

Donc, en réponse partielle avant de continuer : Avoir à la fois une interface et une classe de base a du sens uniquement si vous décidez de coder en fonction d'une interface en premier lieu. Si vous autorisez une interface, vous devez coder uniquement en fonction de cette interface, car sinon vous violeriez le Principe de substitution de Liskov. En d'autres termes, même si vous fournissez une classe de base qui implémente l'interface, vous ne pouvez pas laisser votre code consommer cette classe de base.

Si vous décidez de coder en fonction d'une classe de base, avoir une interface n'a aucun sens.

Si vous décidez de coder en fonction d'une interface, avoir une classe de base qui fournit une fonctionnalité par défaut est facultatif. Ce n'est pas nécessaire, mais cela peut accélérer les choses pour les implémenteurs, vous pouvez donc en fournir un en tant que courtoisie.

Un exemple qui me vient à l'esprit est dans ASP.NET MVC. Le pipeline de requêtes fonctionne sur IController, mais il y a une classe de base Controller que vous utilisez généralement pour implémenter un comportement.

Réponse finale : Si vous utilisez une classe de base abstraite, utilisez uniquement celle-ci. Si vous utilisez une interface, une classe de base est une courtoisie facultative pour les implémenteurs.


Mise à jour : Je ne préfère plus les classes abstraites aux interfaces, et ce depuis longtemps ; à la place, je privilégie la composition à l'héritage, en utilisant SOLID comme guide.

(Bien que je puisse modifier directement le texte ci-dessus, cela changerait radicalement la nature de l'article, et comme quelques personnes l'ont jugé suffisamment utile pour le voter favorablement, je préfère laisser le texte original tel qu'il est, et ajouter plutôt cette note. La dernière partie de l'article a encore du sens, il serait donc dommage de la supprimer également.)

20voto

kyoryu Points 8281

Je tends à utiliser des classes de base (abstraites ou non) pour décrire ce que quelque chose est, tandis que j'utilise des interfaces pour décrire les capacités d'un objet.

Un Chat est un mammifère mais l'une de ses capacités est d'être caressé.

Ou, pour le dire d'une autre façon, les classes sont des noms, tandis que les interfaces se rapprochent plus des adjectifs.

12voto

Kishor Points 111

Depuis MSDN, Recommandations pour les classes abstraites vs. les interfaces

  • Si vous prévoyez de créer plusieurs versions de votre composant, créez une classe abstraite. Les classes abstraites offrent un moyen simple et facile de versionner vos composants. En mettant à jour la classe de base, toutes les classes filles sont automatiquement mises à jour avec le changement. Les interfaces, en revanche, ne peuvent pas être modifiées une fois créées. Si une nouvelle version d'une interface est nécessaire, vous devez créer une toute nouvelle interface.

  • Si la fonctionnalité que vous créez sera utile sur un large éventail d'objets disparates, utilisez une interface. Les classes abstraites doivent être utilisées principalement pour des objets étroitement liés, tandis que les interfaces sont mieux adaptées pour fournir des fonctionnalités communes à des classes non liées.

  • Si vous concevez de petites fonctionnalités concises, utilisez des interfaces. Si vous concevez de grandes unités fonctionnelles, utilisez une classe abstraite.

  • Si vous souhaitez fournir une fonctionnalité commune et implémentée parmi toutes les implémentations de votre composant, utilisez une classe abstraite. Les classes abstraites vous permettent d'implémenter partiellement votre classe, tandis que les interfaces ne contiennent aucune implémentation pour aucun membre.

2voto

Frans Bouma Points 6015

Je suis d'utiliser toujours ces lignes directrices :

  • Utilisez des interfaces pour une inheritance multiple de TYPE (car .NET/Java n'utilisent pas l'héritage multiple)
  • Utilisez des classes abstraites pour une implémentation réutilisable d'un type

La règle du souci dominant dicte qu'une classe a toujours un souci principal et 0 ou plusieurs autres (voir http://citeseer.ist.psu.edu/tarr99degrees.html). Ces 0 ou plusieurs autres sont ensuite implémentés à travers des interfaces, car la classe implémente ensuite tous les types qu'elle doit implémenter (les siens propres et toutes les interfaces qu'elle implémente).

Dans un monde d'héritage d'implémentation multiple (par exemple en C++/Eiffel), on hériterait des classes qui implémentent les interfaces. (En théorie. En pratique, cela pourrait ne pas fonctionner si bien.)

2voto

starblue Points 29696

Si vous souhaitez offrir la possibilité de remplacer complètement votre implémentation, utilisez une interface. Cela s'applique particulièrement aux interactions entre les principaux composants, qui devraient toujours être découplés par des interfaces.

Il peut aussi y avoir des raisons techniques de préférer une interface, par exemple pour permettre le mocking dans les tests unitaires.

À l'intérieur d'un composant, il peut être bon d'utiliser directement une classe abstraite pour accéder à une hiérarchie de classes.

Si vous utilisez une interface et avez une hiérarchie de classes d'implémentation, il est recommandé d'avoir une classe abstraite qui contient les parties communes de l'implémentation.

interface Foo
abstract class FooBase implements Foo
class FunnyFoo extends FooBase
class SeriousFoo extends FooBase

Vous pouvez également avoir des classes abstraites se succédant les unes aux autres pour une hiérarchie plus compliquée.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X