Bien que certaines directives indiquent que vous devez 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'un autre (Cat : Mammal
, Snake : Reptile
), il y a des cas où mon avis) ces lignes directrices entrent dans une zone grise.
Par exemple, supposons que mon implémentation était Cat : Pet
. Pet
est une classe abstraite. Devrait-il être étendu à Cat : Mammal, IDomesticated
où Mammal
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 Wolf
à l'avenir, qui ne pourrait pas hériter de Pet
)?
S'éloignant des métadhores Cat
s et Pet
s, supposons que j'ai des classes qui représentent des sources pour les données entrantes. Ils doivent tous mettre en œuvre la même base d'une manière ou d'une autre. Je pourrais implémenter du code générique dans une classe abstraite Source
et en hériter. Je pourrais aussi simplement créer une interface ISource
(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 gâteau et le manger" en faisant à la fois la classe abstraite et l'interface. Quel est le meilleur?
Ces deux cas soulèvent des points pour utiliser uniquement une classe abstraite, uniquement une interface et utiliser à la fois une classe abstraite et une interface. S'agit-il de choix valables, ou existe-t-il des "règles" pour le choix du type d'utilisation?
J'aimerais préciser que "en utilisant à la fois une classe abstraite et une interface" qui inclut le cas où elles représentent essentiellement la même chose (Source
et ISource
ont les mêmes membres), mais la classe ajoute une fonctionnalité générique alors que l'interface spécifie le contrat.
Il convient également de noter que cette question s'adresse principalement aux langues qui ne prennent pas en charge l'héritage multiple (comme .NET et Java).
En règle générale, je préfère les classes abstraites aux interfaces, d'après les règles de conception .NET . Le raisonnement s’applique beaucoup plus largement que .NET, mais il est mieux expliqué dans le livre Framework Design Guidelines .
La gestion des versions est le raisonnement principal derrière la préférence pour les classes de base abstraites, car vous pouvez toujours ajouter un nouveau membre virtuel à une classe de base abstraite sans détruire les clients existants. Ce n'est pas possible avec les interfaces.
Il existe des scénarios dans lesquels une interface reste le bon choix (en particulier lorsque vous ne vous souciez pas du contrôle de version), mais connaître les avantages et les inconvénients de cette option vous permet de prendre la bonne décision.
Donc, comme réponse partielle avant de continuer: avoir à la fois une interface et une classe de base n’a de sens que si vous décidez de coder une interface en premier lieu. Si vous autorisez une interface, vous devez coder uniquement contre cette interface, 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 utiliser cette classe de base.
Si vous décidez de coder avec une classe de base, avoir une interface n'a aucun sens.
Si vous décidez de coder par rapport à une interface, il est facultatif d’avoir une classe de base fournissant des fonctionnalités par défaut. Ce n'est pas nécessaire, mais cela peut accélérer les choses pour les développeurs, vous pouvez donc en fournir un par courtoisie.
Un exemple qui me vient à l’esprit est ASP.NET MVC. Le pipeline de demandes fonctionne sur IController, mais il existe une classe de base de contrôleur que vous utilisez généralement pour implémenter le comportement.
Réponse finale: Si vous utilisez une classe de base abstraite, utilisez-la uniquement. Si vous utilisez une interface, une classe de base est une courtoisie facultative pour les développeurs.
Update: I pas plus préfère les classes abstraites aux interfaces, et ce n'est pas le cas depuis longtemps; au lieu de cela, je préfère la composition à l'héritage, en utilisant SOLID comme guide.
(Même si je pouvais éditer directement le texte ci-dessus, cela changerait radicalement la nature du message. Etant donné que quelques personnes l'ont trouvé suffisamment précieux pour le voter à la hausse, je préfère laisser le texte original tenir Remarque: la dernière partie du message est toujours utile, il serait donc dommage de le supprimer également.)
J'ai tendance à utiliser des classes de base (abstraites ou non) pour décrire ce que quelque chose est , alors 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 qu'il est piquable.
Autrement dit, les classes sont des noms, tandis que les interfaces se rapprochent des adjectifs.
From MSDN, Recommandations pour les classes abstraites et les interfaces
Si vous envisagez de créer plusieurs versions de votre composant, créez une classe abstraite. Les classes abstraites offrent un moyen simple et facile de mettre à jour vos composants. En mettant à jour la classe de base, toutes les classes héritées sont automatiquement mises à jour avec la modification. Les interfaces, en revanche, ne peuvent pas être modifiées une fois créées. Si une nouvelle version d'une interface est requise, vous devez créer une toute nouvelle interface.
Si la fonctionnalité que vous créez sera utile pour une large gamme d'objets disparates, utilisez une interface. Les classes abstraites doivent être utilisées principalement pour les objets étroitement liés, tandis que les interfaces conviennent mieux à la fourniture de 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 des fonctionnalités communes et implémentées parmi toutes les implémentations de votre composant, utilisez une classe abstraite. Les classes abstraites vous permettent d'implémenter partiellement votre classe, alors que les interfaces ne contiennent aucune implémentation pour les membres.
Il y a aussi quelque chose appelé le principe SEC - Ne vous répétez pas.
Dans votre exemple de sources de données, vous dites qu'il existe un code générique commun à différentes implémentations. Il me semble que le meilleur moyen de gérer cela serait d’avoir une classe abstraite contenant le code générique et quelques classes concrètes l’étendant.
L'avantage est que chaque correction de bogue dans le code générique bénéficie à toutes les implémentations concrètes.
Si vous n'utilisez que l'interface, vous devrez conserver plusieurs copies du même code, ce qui pose problème.
En ce qui concerne abstract + interface, s'il n'y a pas de justification immédiate, je ne le ferais pas. Extraire l’interface de la classe abstraite est un refactoring facile, je ne le ferais donc que lorsque c’est vraiment nécessaire.
J'utilise toujours ces directives:
La règle de la préoccupation dominante dicte qu'une classe a toujours une préoccupation principale et 0 ou plus (voir http://citeseer.ist.psu.edu/tarr99degrees.html ). Ceux de 0 ou plus que vous implémentez ensuite via des interfaces, la classe implémentant ensuite tous les types qu’elle doit implémenter (le sien, et toutes les interfaces qu’elle implémente).
Dans un monde caractérisé par plusieurs héritages en implémentation (par exemple C++/Eiffel), on hériterait des classes qui implémentent les interfaces. (En théorie. En pratique, cela pourrait ne pas bien fonctionner.)
Si vous souhaitez fournir la possibilité de remplacer complètement votre implémentation, utilisez une interface. Cela s'applique particulièrement aux interactions entre les composants principaux, ceux-ci doivent toujours être découplés par les interfaces.
Il peut également y avoir des raisons techniques pour préférer une interface, par exemple pour permettre la simulation dans des tests unitaires.
En interne, dans un composant, il peut être judicieux d'utiliser une classe abstraite directement pour accéder à une hiérarchie de classes.
Si vous utilisez une interface et que vous avez une hiérarchie d'implémentations de classes, il est recommandé de disposer d'une classe abstraite contenant les parties communes de l'implémentation. Par exemple.
interface Foo
abstract class FooBase implements Foo
class FunnyFoo extends FooBase
class SeriousFoo extends FooBase
Vous pouvez également avoir plus de classes abstraites héritant les unes des autres pour une hiérarchie plus compliquée.
Reportez-vous à la question SE ci-dessous pour des instructions génériques:
Interface vs Abstract Class (OO général)
Cas d'utilisation pratique pour l'interface:
Implémentation de Strategy_pattern : Définissez votre stratégie en tant qu'interface. Basculez la mise en œuvre de manière dynamique avec l'une des mises en œuvre concrètes de la stratégie au moment de l'exécution.
Définissez une capacité parmi plusieurs classes non liées.
Cas d'utilisation pratique pour la classe abstraite:
Implémentation de Template_method_pattern : Définit un squelette d'un algorithme. Les classes enfants ne peuvent pas changer la structure de l'algorithme, mais elles peuvent redéfinir une partie de l'implémentation dans les classes enfants.
Lorsque vous souhaitez partager des variables non statiques et non finales entre plusieurs classes liées avec une relation "a une".
Utilisation à la fois de la classe abstraite et de l'interface:
Si vous optez pour une classe abstraite, vous pouvez déplacer des méthodes abstraites vers une interface et la classe abstraite peut simplement implémenter cette interface. Tous les cas d'utilisation de classes abstraites peuvent entrer dans cette catégorie.