Quand utiliser une interface au lieu d’une classe abstraite et vice versa?

Cela peut être une question générique de POO. Je voulais faire une comparaison générique entre une interface et une classe abstraite en fonction de leur utilisation.

Quand voudrait-on utiliser une interface et quand voudrait-on utiliser une classe abstraite ?

J’ai écrit un article à ce sujet:

Classes abstraites et interfaces

Résumé:

Lorsque nous parlons de classes abstraites, nous définissons les caractéristiques d’un type d’object. spécifiant ce qu’est un object .

Lorsque nous parlons d’une interface et définissons les capacités que nous nous engageons à fournir, nous parlons d’établir un contrat sur ce que l’object peut faire.

Une classe abstraite peut avoir un état ou une fonctionnalité partagée. Une interface n’est qu’une promesse de fournir l’état ou la fonctionnalité. Un bon cours de résumé réduira la quantité de code à réécrire car ses fonctionnalités ou son état peuvent être partagés. L’interface n’a aucune information définie à partager

Personnellement, je n’ai presque jamais besoin d’écrire des cours abstraits.

La plupart du temps, je vois des classes abstraites qui sont (mal) utilisées, c’est parce que l’auteur de la classe abstraite utilise le pattern “Template method”.

Le problème avec la “méthode du modèle” est qu’il est presque toujours quelque peu réentrant – la classe “dérivée” connaît non seulement la méthode “abstraite” de sa classe de base qu’elle implémente, mais aussi les méthodes publiques de la classe de base. , même si la plupart du temps, il n’est pas nécessaire de les appeler.

Exemple (trop simplifié):

 abstract class QuickSorter { public void Sort(object[] items) { // implementation code that somewhere along the way calls: bool less = compare(x,y); // ... more implementation code } abstract bool compare(object lhs, object rhs); } 

Donc, ici, l’auteur de cette classe a écrit un algorithme générique et souhaite que les gens l’utilisent en le “spécialisant” en fournissant leurs propres “hooks” – dans ce cas, une méthode “compare”.

Donc, l’utilisation prévue est quelque chose comme ceci:

 class NameSorter : QuickSorter { public bool compare(object lhs, object rhs) { // etc. } } 

Le problème est que vous avez indûment couplé deux concepts:

  1. Une façon de comparer deux éléments (quel article devrait aller en premier)
  2. Une méthode de sorting des éléments (par exemple sorting rapide, sorting par fusion, etc.)

Dans le code ci-dessus, en théorie, l’auteur de la méthode “compare” peut ré-entrer de nouveau dans la super-classe “Sort” … même si, dans la pratique, ils ne voudront ou n’auront jamais besoin de le faire.

Le prix que vous payez pour ce couplage inutile est qu’il est difficile de changer la superclasse, et dans la plupart des langages OO, impossible de la modifier à l’exécution.

La méthode alternative consiste à utiliser le modèle de conception “Stratégie” à la place:

 interface IComparator { bool compare(object lhs, object rhs); } class QuickSorter { private readonly IComparator comparator; public QuickSorter(IComparator comparator) { this.comparator = comparator; } public void Sort(object[] items) { // usual code but call comparator.Compare(); } } class NameComparator : IComparator { bool compare(object lhs, object rhs) { // same code as before; } } 

Alors remarquez maintenant: tout ce que nous avons, ce sont des interfaces et des implémentations concrètes de ces interfaces. En pratique, vous n’avez pas vraiment besoin de quoi que ce soit d’autre pour réaliser un design OO de haut niveau.

Pour “cacher” le fait que nous avons implémenté le “sorting des noms” en utilisant une classe “QuickSort” et un “NameComparator”, nous pourrions toujours écrire une méthode de fabrication quelque part:

 ISorter CreateNameSorter() { return new QuickSorter(new NameComparator()); } 

Chaque fois que vous avez une classe abstraite, vous pouvez le faire … même s’il existe une relation rentrante naturelle entre la classe de base et la classe dérivée, il est généralement avantageux de les rendre explicites.

Une dernière pensée: tout ce que nous avons fait ci-dessus est de “composer” une fonction “NameSorting” en utilisant une fonction “QuickSort” et une fonction “NameComparison” … dans un langage de programmation fonctionnel, ce style de programmation devient encore plus naturel, avec moins de code.

OK, je viens juste de “grokked” moi-même – la voici en termes simples (n’hésitez pas à me corriger si je me trompe) – Je sais que ce sujet est trop long, mais quelqu’un d’autre pourrait le rencontrer un jour …

Les classes abstraites vous permettent de créer un modèle, et vous permettent également de CONSTRUIRE (implémenter) des propriétés et des méthodes que vous souhaitez que TOUS ses descendants possèdent.

Une interface par contre vous permet seulement de déclarer que vous voulez que les propriétés et / ou les méthodes avec un nom donné existent dans toutes les classes qui l’implémentent – mais ne spécifie pas comment vous devez les implémenter. De même, une classe peut implémenter des interfaces MANY, mais ne peut étendre que la classe ONE Abstract. Une interface est plutôt un outil architectural de haut niveau (qui devient plus clair si vous commencez à saisir des modèles de conception) – un résumé a un pied dans les deux camps et peut également effectuer certains travaux délicats.

Pourquoi utiliser l’un sur l’autre? Le premier permet une définition plus concrète des descendants – le second permet un plus grand polymorphism . Ce dernier point est important pour l’utilisateur final / le codeur, qui peut utiliser ces informations pour mettre en œuvre l’AP I (interface) dans diverses combinaisons / formes pour répondre à leurs besoins.

Je pense que ce fut le moment “lightbulb” pour moi – pensez moins aux interfaces de la perspective de l’auteur et plus à celle de n’importe quel codeur venant plus tard dans la chaîne qui ajoute l’implémentation à un projet ou étend une API.

Mes deux centimes:

Une interface définit essentiellement un contrat auquel toute classe d’implémentation doit adhérer (implémenter les membres de l’interface). Il ne contient aucun code.

D’un autre côté, une classe abstraite peut contenir du code, et il peut y avoir des méthodes marquées comme abstraites qu’une classe héritière doit implémenter.

Les rares situations où j’ai utilisé des classes abstraites, c’est lorsque j’ai des fonctionnalités par défaut que la classe héritière pourrait ne pas être intéressante pour substituer, par exemple une classe de base abstraite, dont certaines classes spécialisées héritent.

Exemple (très rudimentaire!): Considérons une classe de base appelée Customer qui a des méthodes abstraites telles que CalculatePayment() , CalculateRewardPoints() et des méthodes non abstraites telles que GetName() , SavePaymentDetails() .

Des classes spécialisées comme RegularCustomer et GoldCustomer hériteront de la classe de base Customer et implémenteront leur propre logique de méthode CalculatePayment() et CalculateRewardPoints() , mais réutiliseront les GetName() et SavePaymentDetails() .

Vous pouvez append plus de fonctionnalités à une classe abstraite (méthodes non abstraites) sans affecter les classes enfants utilisant une version antérieure. Considérant que l’ajout de méthodes à une interface affecterait toutes les classes l’implémentant car elles devraient maintenant implémenter les membres de l’interface nouvellement ajoutés.

Une classe abstraite avec tous les membres abstraits serait similaire à une interface.

Quand faire ce qui est une chose très simple si vous avez le concept clair dans votre esprit.

Les classes abstraites peuvent être dérivées, tandis que les interfaces peuvent être implémentées. Il y a une différence entre les deux. Lorsque vous dérivez une classe Abstract, la relation entre la classe dérivée et la classe de base est la relation ‘is a’. Par exemple, un chien est un animal, un mouton est un animal, ce qui signifie qu’une classe dérivée hérite de certaines propriétés de la classe de base.

Alors que pour l’implémentation des interfaces, la relation est “peut être”. Par exemple, un chien peut être un chien espion. Un chien peut être un chien de cirque. Un chien peut être un chien de course. Ce qui signifie que vous implémentez certaines méthodes pour acquérir quelque chose.

J’espère que je suis clair.

Si vous regardez java comme langage OOP,

“L’ interface ne fournit pas d’implémentation de méthode ” n’est plus valide avec le lancement de Java 8. Maintenant, java fournit une implémentation dans l’interface pour les méthodes par défaut.

En termes simples, je voudrais utiliser

interface: Pour implémenter un contrat par plusieurs objects non liés. Il fournit une capacité ” HAS A “.

classe abstraite: Pour implémenter le même comportement ou un comportement différent parmi plusieurs objects liés. Il établit une relation ” IS A “.

Le site Web Oracle offre des différences essentielles entre l’ interface et abstract classe abstract .

Envisagez d’utiliser des classes abstraites si:

  1. Vous voulez partager du code entre plusieurs classes étroitement liées.
  2. Vous vous attendez à ce que les classes qui étendent votre classe abstraite disposent de nombreuses méthodes ou champs communs, ou requièrent des modificateurs d’access autres que publics (tels que protégés et privés).
  3. Vous souhaitez déclarer des champs non statiques ou non finaux.

Envisagez d’utiliser des interfaces si:

  1. Vous vous attendez à ce que des classes non liées implémentent votre interface. Par exemple, de nombreux objects non liés peuvent implémenter une interface Serializable .
  2. Vous souhaitez spécifier le comportement d’un type de données particulier, mais ne vous préoccupez pas de savoir qui implémente son comportement.
  3. Vous voulez profiter de l’inheritance multiple de type.

Exemple:

Classe abstraite (relation IS A )

Reader est une classe abstraite.

BufferedReader est un Reader

FileReader est un Reader

FileReader et BufferedReader sont utilisés à des fins communes: lecture de données et elles sont liées par la classe Reader .

Interface (capacité HAS A )

Serializable est une interface.

Supposons que vous ayez deux classes dans votre application, qui implémentent une interface Serializable

Employee implements Serializable

Game implements Serializable

Ici, vous ne pouvez pas établir de relation via l’interface Serializable entre Employee et Game , qui sont destinés à des fins différentes. Les deux sont capables de sérialiser l’état et la comparaison se termine là.

Regardez ces articles:

Comment devrais-je expliquer la différence entre une interface et une classe abstraite?

J’ai écrit un article sur quand utiliser une classe abstraite et quand utiliser une interface. Il y a beaucoup plus de différence entre eux que “un IS-A … et un CAN-DO …”. Pour moi, ce sont des réponses en boîte. Je mentionne quelques raisons pour utiliser l’un ou l’autre. J’espère que cela aide.

http://codeofdoom.com/wordpress/2009/02/12/learn-this-when-to-use-an-abstract-class-and-an-interface/

1.Si vous créez quelque chose qui fournit des fonctionnalités communes à des classes sans rapport, utilisez une interface.

2.Si vous créez quelque chose pour des objects étroitement liés dans une hiérarchie, utilisez une classe abstraite.

Les classes peuvent hériter d’une seule classe de base, donc si vous voulez utiliser des classes abstraites pour fournir un polymorphism à un groupe de classes, elles doivent toutes hériter de cette classe. Les classes abstraites peuvent également fournir des membres qui ont déjà été implémentés. Par conséquent, vous pouvez garantir une certaine quantité de fonctionnalités identiques avec une classe abstraite, mais ne pouvez pas avec une interface.

Voici quelques recommandations pour vous aider à décider d’utiliser une interface ou une classe abstraite pour fournir un polymorphism à vos composants.

  • 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 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, quant à elles, ne peuvent pas être modifiées une fois créées de cette manière. Si une nouvelle version d’une interface est requirejse, vous devez créer une toute nouvelle interface.
  • Si la fonctionnalité que vous créez sera utile sur un large éventail d’objects disparates, utilisez une interface. Les classes abstraites doivent être utilisées principalement pour les objects étroitement liés, tandis que les interfaces sont les mieux adaptées pour fournir des fonctionnalités communes aux classes non apparenté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 voulez 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 aucun membre.

Copié de:
http://msdn.microsoft.com/en-us/library/scsyfw1d%28v=vs.71%29.aspx

Envisagez d’utiliser des classes abstraites si l’une de ces déclarations s’applique à votre situation:

  1. Vous voulez partager du code entre plusieurs classes étroitement liées.
  2. Vous vous attendez à ce que les classes qui étendent votre classe abstraite disposent de nombreuses méthodes ou champs communs ou requièrent des modificateurs d’access autres que publics (tels que protégés et privés).
  3. Vous souhaitez déclarer des champs non statiques ou non finaux. Cela vous permet de définir des méthodes pouvant accéder et modifier l’état de l’object auquel elles appartiennent.

Envisagez d’utiliser des interfaces si l’une de ces déclarations s’applique à votre situation:

  1. Vous vous attendez à ce que des classes non liées implémentent votre interface. Par exemple, les interfaces Comparable et Cloneable sont implémentées par de nombreuses classes non liées.
  2. Vous souhaitez spécifier le comportement d’un type de données particulier, mais ne vous préoccupez pas de savoir qui implémente son comportement.
  3. Vous souhaitez tirer parti de plusieurs inheritances.

La source

Les réponses varient selon les langues. Par exemple, en Java, une classe peut implémenter (hériter de) plusieurs interfaces mais hériter d’une seule classe abstraite. Les interfaces vous offrent donc plus de flexibilité. Mais ce n’est pas vrai en C ++.

Je pense que la manière la plus succincte de le dire est la suivante:

Propriétés partagées => classe abstraite.
Fonctionnalité partagée => interface.

Et pour le dire moins succinctement …

Exemple de classe abstraite:

 public abstract class BaseAnimal { public int NumberOfLegs { get; set; } protected BaseAnimal(int numberOfLegs) { NumberOfLegs = numberOfLegs; } } public class Dog : BaseAnimal { public Dog() : base(4) { } } public class Human : BaseAnimal { public Human() : base(2) { } } 

Puisque les animaux ont une propriété partagée – nombre de twigs dans ce cas – il est logique de créer une classe abstraite contenant cette propriété partagée. Cela nous permet également d’écrire un code commun qui fonctionne sur cette propriété. Par exemple:

 public static int CountAllLegs(List animals) { int legCount = 0; foreach (BaseAnimal animal in animals) { legCount += animal.NumberOfLegs; } return legCount; } 

Exemple d’interface:

 public interface IMakeSound { void MakeSound(); } public class Car : IMakeSound { public void MakeSound() => Console.WriteLine("Vroom!"); } public class Vuvuzela : IMakeSound { public void MakeSound() => Console.WriteLine("VZZZZZZZZZZZZZ!"); } 

Notez ici que Vuvuzelas et Cars sont des choses complètement différentes, mais elles ont une fonctionnalité partagée: créer un son. Ainsi, une interface a du sens ici. En outre, il permettra aux programmeurs de regrouper les éléments qui IMakeSound sons sous une interface commune – IMakeSound dans ce cas. Avec cette conception, vous pouvez écrire le code suivant:

 List soundMakers = new List(); soundMakers.Add(new Car()); soundMakers.Add(new Vuvuzela()); soundMakers.Add(new Car()); soundMakers.Add(new Vuvuzela()); soundMakers.Add(new Vuvuzela()); foreach (IMakeSound soundMaker in soundMakers) { soundMaker.MakeSound(); } 

Pouvez-vous dire ce que cela produirait?

Enfin, vous pouvez combiner les deux.

Exemple combiné:

 public interface IMakeSound { void MakeSound(); } public abstract class BaseAnimal : IMakeSound { public int NumberOfLegs { get; set; } protected BaseAnimal(int numberOfLegs) { NumberOfLegs = numberOfLegs; } public abstract void MakeSound(); } public class Cat : BaseAnimal { public Cat() : base(4) { } public override void MakeSound() => Console.WriteLine("Meow!"); } public class Human : BaseAnimal { public Human() : base(2) { } public override void MakeSound() => Console.WriteLine("Hello, world!"); } 

Ici, nous exigeons que tous les BaseAnimal émettent un son, mais nous ne connaissons pas encore son implémentation. Dans un tel cas, nous pouvons abstraire l’implémentation de l’interface et déléguer son implémentation à ses sous-classes.

Un dernier point, rappelez-vous comment, dans l’exemple de la classe abstraite, nous avons pu opérer sur les propriétés partagées de différents objects et dans l’exemple d’interface, nous avons pu invoquer la fonctionnalité partagée de différents objects? Dans ce dernier exemple, nous pourrions faire les deux.

Utilisez une classe abstraite si vous souhaitez fournir des implémentations de base.

en Java, vous pouvez hériter d’une classe (abstraite) pour “fournir” des fonctionnalités et vous pouvez implémenter de nombreuses interfaces pour “garantir” les fonctionnalités

Pour des raisons purement héréditaires, vous utiliseriez un résumé où vous définissez clairement des relations abstraites descendantes (c.-à-d. Animal-> cat) et / ou exigez l’inheritance de propriétés virtuelles ou non publiques, en particulier l’état partagé (que les interfaces ne peuvent pas prendre en charge). ).

Vous devriez essayer de privilégier la composition (via l’dependency injection) plutôt que l’inheritance, et noter que les interfaces en tant que contrats prennent en charge les tests unitaires, la séparation des préoccupations et l’inheritance multiple.

Un endroit intéressant où les interfaces se comportent mieux que les classes abstraites est lorsque vous devez append des fonctionnalités supplémentaires à un groupe d’objects (associés ou non). Si vous ne pouvez pas leur donner une classe abstraite de base (par exemple, ils sont sealed ou ont déjà un parent), vous pouvez leur donner une interface factice (vide) à la place, puis simplement écrire des méthodes d’extension pour cette interface.

Cela peut être un appel très difficile à faire …

Un pointeur que je peux donner: un object peut implémenter de nombreuses interfaces, alors qu’un object ne peut hériter que d’une classe de base (dans un langage OO moderne comme c #, je sais que C ++ a plusieurs inheritances – mais cela n’est-il pas mal vu?)

Une classe abstraite peut avoir des implémentations.

Une interface n’a pas d’implémentations, elle définit simplement une sorte de contrat.

Il peut également y avoir des différences dépendantes de la langue: par exemple, C # n’a pas d’inheritance multiple, mais plusieurs interfaces peuvent être implémentées dans une classe.

Pour moi, j’irais souvent avec des interfaces. Mais je préfère les classes abstraites dans certains cas.

Les classes dans OO font généralement référence à la mise en œuvre. J’utilise des classes abstraites quand je veux forcer certains détails d’implémentation aux enfants, sinon je vais avec des interfaces.

Bien entendu, les classes abstraites sont utiles non seulement pour forcer la mise en œuvre, mais aussi pour partager certains détails spécifiques entre de nombreuses classes apparentées.

La règle de base du pouce est la suivante: Pour “Nouns”, utilisez la classe Abstract et pour “Verbs” utilisez l’interface

Par exemple: la car est une classe abstraite et un drive , nous pouvons en faire une interface.