Question

Quand dois-je utiliser une interface et quand dois-je utiliser une classe de base ?

Doit-il toujours s'agir d'une interface si je ne souhaite pas réellement définir une implémentation de base des méthodes ?

Si j'ai un cours Chien et Chat.Pourquoi voudrais-je implémenter IPet au lieu de PetBase ?Je peux comprendre avoir des interfaces pour ISheds ou IBarks (IMakesNoise ?), car celles-ci peuvent être placées animal par animal, mais je ne comprends pas laquelle utiliser pour un animal générique.

Était-ce utile?

La solution

Prenons votre exemple d'une classe Chien et Chat, et illustrons en utilisant C# :

Un chien et un chat sont tous deux des animaux, en particulier des mammifères quadrupèdes (les animaux sont beaucoup trop généraux).Supposons que vous ayez une classe abstraite Mammal, pour les deux :

public abstract class Mammal

Cette classe de base aura probablement des méthodes par défaut telles que :

  • Alimentation
  • Copain

Ce sont tous des comportements qui ont plus ou moins la même mise en œuvre entre les deux espèces.Pour définir cela vous aurez :

public class Dog : Mammal
public class Cat : Mammal

Supposons maintenant qu'il existe d'autres mammifères, que nous verrons habituellement dans un zoo :

public class Giraffe : Mammal
public class Rhinoceros : Mammal
public class Hippopotamus : Mammal

Cela sera toujours valable car au cœur de la fonctionnalité Feed() et Mate() sera toujours le même.

Cependant, les girafes, les rhinocéros et les hippopotames ne sont pas exactement des animaux dont on peut faire des animaux de compagnie.C'est là qu'une interface sera utile :

public interface IPettable
{
    IList<Trick> Tricks{get; set;}
    void Bathe();
    void Train(Trick t);
}

La mise en œuvre du contrat ci-dessus ne sera pas la même entre un chat et un chien ;mettre leurs implémentations dans une classe abstraite pour en hériter sera une mauvaise idée.

Vos définitions de chien et de chat devraient maintenant ressembler à :

public class Dog : Mammal, IPettable
public class Cat : Mammal, IPettable

Théoriquement, vous pouvez les remplacer à partir d'une classe de base supérieure, mais essentiellement, une interface vous permet d'ajouter uniquement les éléments dont vous avez besoin dans une classe sans avoir besoin d'héritage.

Par conséquent, comme vous ne pouvez généralement hériter que d'une seule classe abstraite (dans la plupart des langages OO typés statiquement, c'est...les exceptions incluent C++) mais étant capable d'implémenter plusieurs interfaces, cela vous permet de construire des objets de manière strictement comme demandé base.

Autres conseils

Eh bien, Josh Bloch s'est dit dans Java 2d efficace:

Préférez les interfaces aux classes abstraites

Quelques points principaux :

  • Les classes existantes peuvent être facilement modernisées pour implémenter une nouvelle interface.Tout ce que vous avez à faire est d'ajouter les méthodes requises si elles n'existent pas encore et d'ajouter une clause d'implémente à la déclaration de classe.

  • Les interfaces sont idéales pour définir des mixins.De plus en plus, un mixin est un type qu'une classe peut implémenter en plus de son «type principal» pour déclarer qu'il fournit un comportement facultatif.Par exemple, comparable est une interface de mixin qui permet à une classe de déclarer que ses instances sont commandées par rapport à d'autres objets mutuellement comparables.

  • Les interfaces permettent la construction de cadres de type non hiérarchiques.Les hiérarchies de type sont idéales pour organiser certaines choses, mais d'autres ne tombent pas parfaitement dans une hiérarchie rigide.

  • Les interfaces permettent des améliorations de fonctionnalités sûres et puissantes via l'idiome de la classe.Si vous utilisez des classes abstraites pour définir des types, vous quittez le programmeur qui veut ajouter des fonctionnalités sans alternative que pour utiliser l'héritage.

De plus, vous pouvez combiner les vertus des interfaces et des classes abstraites en fournissant une classe d'implémentation squelettique abstraite pour accompagner chaque interface non triviale que vous exportez.

En revanche, les interfaces sont très difficiles à faire évoluer.Si vous ajoutez une méthode à une interface, toutes ses implémentations seront interrompues.

PS. :Achetez le livre.C'est beaucoup plus détaillé.

Le style moderne doit définir IPet et Base pour animaux de compagnie.

L'avantage de l'interface est que d'autres codes peuvent l'utiliser sans aucun lien avec un autre code exécutable.Complètement «propre». Les interfaces peuvent également être mélangées.

Mais les classes de base sont utiles pour les implémentations simples et les utilitaires courants.Fournissez donc également une classe de base abstraite pour gagner du temps et du code.

Les interfaces et les classes de base représentent deux formes différentes de relations.

Héritage (classes de base) représentent une relation « est-un ».Par exemple.un chien ou un chat « est un » animal de compagnie.Cette relation représente toujours le (unique) but de la classe (en collaboration avec le "principe de responsabilité unique").

Interfaces, en revanche, représentent caractéristiques supplémentaires d'une classe.J'appellerais ça une relation "est", comme dans "Foo est jetable", d'où le IDisposable interface en C#.

Interfaces

  • Définit le contrat entre 2 modules.Ne peut avoir aucune implémentation.
  • La plupart des langages vous permettent d'implémenter plusieurs interfaces
  • Modifier une interface est un changement radical.Toutes les implémentations doivent être recompilées/modifiées.
  • Tous les membres sont publics.Les implémentations doivent implémenter tous les membres.
  • Les interfaces aident au découplage.Vous pouvez utiliser des frameworks simulés pour simuler tout ce qui se cache derrière une interface
  • Les interfaces indiquent normalement un type de comportement
  • Les implémentations d'interface sont découplées/isolées les unes des autres

Cours de base

  • Permet d'en ajouter défaut implémentation que vous obtenez gratuitement par dérivation
  • Sauf en C++, vous ne pouvez dériver qu’à partir d’une seule classe.Même si cela peut provenir de plusieurs classes, c'est généralement une mauvaise idée.
  • Changer la classe de base est relativement simple.Les dérivations n'ont pas besoin de faire quelque chose de spécial
  • Les classes de base peuvent déclarer des fonctions protégées et publiques accessibles par dérivations
  • Les classes de base abstraites ne peuvent pas être facilement moquées comme les interfaces
  • Les classes de base indiquent normalement la hiérarchie des types (IS A)
  • Les dérivations de classe peuvent dépendre d'un certain comportement de base (avoir une connaissance complexe de l'implémentation parent).Les choses peuvent être compliquées si vous modifiez l'implémentation de base pour un gars et brisez les autres.

En général, vous devez privilégier les interfaces plutôt que les classes abstraites.Une des raisons d'utiliser une classe abstraite est si vous avez une implémentation commune entre les classes concrètes.Bien sûr, vous devez toujours déclarer une interface (IPet) et demander à une classe abstraite (PetBase) d'implémenter cette interface. En utilisant de petites interfaces distinctes, vous pouvez utiliser des multiples pour améliorer encore la flexibilité.Les interfaces permettent un maximum de flexibilité et de portabilité des types au-delà des frontières.Lorsque vous transmettez des références au-delà des limites, transmettez toujours l’interface et non le type concret.Cela permet au destinataire de déterminer la mise en œuvre concrète et offre une flexibilité maximale.Cela est absolument vrai lors de la programmation en mode TDD/BDD.

The Gang of Four a déclaré dans son livre "Parce que l'héritage expose une sous-classe aux détails de l'implémentation de son parent, on dit souvent que" l'héritage brise l'encapsulation ".Je crois que c'est vrai.

C'est assez spécifique à .NET, mais le livre Framework Design Guidelines soutient qu'en général, les classes offrent plus de flexibilité dans un framework en évolution.Une fois qu'une interface est livrée, vous n'avez pas la possibilité de la modifier sans casser le code qui utilisait cette interface.Cependant, avec une classe, vous pouvez la modifier et ne pas casser le code qui y est lié.Tant que vous apportez les bonnes modifications, notamment l’ajout de nouvelles fonctionnalités, vous pourrez étendre et faire évoluer votre code.

Krzysztof Cwalina dit à la page 81 :

Au cours des trois versions du .NET Framework, j'ai parlé de cette directive avec de nombreux développeurs de notre équipe.Beaucoup d'entre eux, y compris ceux qui étaient initialement en désaccord avec les directives, ont déclaré qu'ils regrettaient d'avoir intégré certaines API comme interface.Je n'ai pas entendu parler d'un seul cas dans lequel quelqu'un aurait regretté d'avoir expédié un cours.

Cela étant dit, il y a certainement une place pour les interfaces.En règle générale, fournissez toujours une implémentation de classe de base abstraite d'une interface, ne serait-ce qu'à titre d'exemple de manière d'implémenter l'interface.Dans le meilleur des cas, cette classe de base permettra d'économiser beaucoup de travail.

Juan,

J'aime considérer les interfaces comme un moyen de caractériser une classe.Une classe de race de chien particulière, par exemple un YorkshireTerrier, peut être un descendant de la classe de chien parent, mais elle implémente également IFurry, IStubby et IYippieDog.Ainsi, la classe définit ce qu'est la classe mais l'interface nous en dit des choses.

L'avantage est que cela me permet, par exemple, de rassembler tous les IYippieDog et de les jeter dans ma collection Ocean.Je peux désormais parcourir un ensemble particulier d'objets et trouver ceux qui répondent aux critères que je regarde sans inspecter la classe de trop près.

Je trouve que les interfaces devraient vraiment définir un sous-ensemble du comportement public d'une classe.S'il définit tout le comportement public de toutes les classes qui l'implémentent, il n'a généralement pas besoin d'exister.Ils ne me disent rien d'utile.

Cette pensée va cependant à l’encontre de l’idée selon laquelle chaque classe devrait avoir une interface et que vous devriez coder sur l’interface.C'est bien, mais vous vous retrouvez avec de nombreuses interfaces individuelles vers les classes et cela rend les choses confuses.Je comprends que l’idée est que cela ne coûte vraiment rien et que vous pouvez désormais échanger des éléments facilement.Cependant, je trouve que je fais rarement cela.La plupart du temps, je modifie simplement la classe existante et j'ai exactement les mêmes problèmes que j'ai toujours eu si l'interface publique de cette classe doit être modifiée, sauf que je dois maintenant la modifier à deux endroits.

Donc, si vous pensez comme moi, vous diriez certainement que le chat et le chien sont IPettables.C’est une caractérisation qui leur correspond tous les deux.

L’autre élément est la suivante : devraient-ils avoir la même classe de base ?La question est de savoir s’ils doivent être traités globalement comme la même chose.Ce sont certainement tous les deux des animaux, mais cela correspond-il à la façon dont nous allons les utiliser ensemble ?

Supposons que je souhaite rassembler toutes les classes d'animaux et les mettre dans mon conteneur Ark.

Ou doivent-ils être des mammifères ?Peut-être avons-nous besoin d’une sorte d’usine de traite croisée des animaux ?

Doivent-ils même être reliés entre eux ?Est-il suffisant de savoir qu'ils sont tous les deux IPettables ?

Je ressens souvent le désir de dériver une hiérarchie de classes entière alors que j’ai vraiment besoin d’une seule classe.Je le fais en prévision d’un jour où j’en aurai besoin et généralement je ne le fais jamais.Même lorsque je le fais, je trouve généralement que je dois faire beaucoup pour y remédier.C’est parce que la première classe que je crée n’est pas le Chien, je n’ai pas cette chance, c’est plutôt l’Ornithorynque.Maintenant, toute ma hiérarchie de classes est basée sur le cas bizarre et j'ai beaucoup de code gaspillé.

Vous découvrirez peut-être également à un moment donné que tous les chats ne sont pas IPettables (comme celui sans poils).Vous pouvez maintenant déplacer cette interface vers toutes les classes dérivées qui correspondent.Vous constaterez un changement beaucoup moins radical que tout d'un coup, les chats ne sont plus dérivés de PettableBase.

Voici la définition simple et basique de l’interface et de la classe de base :

  • Classe de base = héritage d'objet.
  • Interface = héritage fonctionnel.

acclamations

Je recommande d'utiliser la composition au lieu de l'héritage autant que possible.Utilisez des interfaces mais utilisez des objets membres pour l'implémentation de base.De cette façon, vous pouvez définir une usine qui construit vos objets pour qu'ils se comportent d'une certaine manière.Si vous souhaitez modifier le comportement, vous créez une nouvelle méthode d'usine (ou usine abstraite) qui crée différents types de sous-objets.

Dans certains cas, vous constaterez peut-être que vos objets principaux n'ont pas du tout besoin d'interfaces, si tout le comportement mutable est défini dans les objets d'assistance.

Ainsi, au lieu d'IPet ou de PetBase, vous pourriez vous retrouver avec un Pet doté d'un paramètre IFurBehavior.Le paramètre IFurBehavior est défini par la méthode CreateDog() de PetFactory.C'est ce paramètre qui est appelé pour la méthode shed().

Si vous faites cela, vous constaterez que votre code est beaucoup plus flexible et que la plupart de vos objets simples traitent des comportements très basiques à l'échelle du système.

Je recommande ce modèle même dans les langages à héritage multiple.

Bien expliqué dans ceci Article du monde Java

Personnellement, j'ai tendance à utiliser des interfaces pour définir des interfaces - c'est-à-direparties de la conception du système qui spécifient comment accéder à quelque chose.

Il n'est pas rare que j'aie une classe implémentant 1 ou plusieurs interfaces.

Des cours abstraits que j'utilise comme base pour autre chose.

Ce qui suit est un extrait de l'article mentionné ci-dessus Article JavaWorld.com, auteur Tony Sintes, 20/04/01


Interface contreclasse abstraite

Choisir des interfaces et des classes abstraites n’est pas une proposition soit/soit.Si vous devez modifier votre design, faites-en une interface.Cependant, vous pouvez avoir des classes abstraites qui fournissent un comportement par défaut.Les classes abstraites sont d’excellents candidats dans les frameworks d’application.

Les classes abstraites vous permettent de définir certains comportements ;ils obligent vos sous-classes à en fournir d'autres.Par exemple, si vous disposez d'un framework d'application, une classe abstraite peut fournir des services par défaut tels que la gestion des événements et des messages.Ces services permettent à votre application de se connecter à votre infrastructure d'application.Cependant, il existe certaines fonctionnalités spécifiques à l'application que seule votre application peut exécuter.Ces fonctionnalités peuvent inclure des tâches de démarrage et d’arrêt, qui dépendent souvent de l’application.Ainsi, au lieu d'essayer de définir ce comportement elle-même, la classe de base abstraite peut déclarer des méthodes abstraites d'arrêt et de démarrage.La classe de base sait qu'elle a besoin de ces méthodes, mais une classe abstraite permet à votre classe d'admettre qu'elle ne sait pas comment effectuer ces actions ;il sait seulement qu'il doit initier les actions.Au moment du démarrage, la classe abstraite peut appeler la méthode de démarrage.Lorsque la classe de base appelle cette méthode, Java appelle la méthode définie par la classe enfant.

De nombreux développeurs oublient qu’une classe qui définit une méthode abstraite peut également appeler cette méthode.Les classes abstraites constituent un excellent moyen de créer des hiérarchies d'héritage planifiées.Ils constituent également un bon choix pour les classes non-feuilles dans les hiérarchies de classes.

Classe contreinterface

Certains disent que vous devriez définir toutes les classes en termes d'interfaces, mais je pense que cette recommandation semble un peu extrême.J'utilise des interfaces lorsque je vois que quelque chose dans ma conception va changer fréquemment.

Par exemple, le modèle Stratégie vous permet d'échanger de nouveaux algorithmes et processus dans votre programme sans modifier les objets qui les utilisent.Un lecteur multimédia sait peut-être lire des CD, des MP3 et des fichiers wav.Bien sûr, vous ne souhaitez pas coder en dur ces algorithmes de lecture dans le lecteur ;cela rendra difficile l'ajout d'un nouveau format comme AVI.De plus, votre code sera rempli d'instructions case inutiles.Et pour ajouter l’insulte à l’injure, vous devrez mettre à jour ces déclarations de cas chaque fois que vous ajoutez un nouvel algorithme.Dans l’ensemble, ce n’est pas une manière de programmer très orientée objet.

Avec le modèle Strategy, vous pouvez simplement encapsuler l’algorithme derrière un objet.Si vous faites cela, vous pouvez fournir de nouveaux plug-ins multimédias à tout moment.Appelons la classe de plug-in MediaStrategy.Cet objet aurait une méthode :playStream (Stream s).Donc, pour ajouter un nouvel algorithme, nous étendons simplement notre classe d'algorithme.Désormais, lorsque le programme rencontre un nouveau type de média, il délègue simplement la lecture du flux à notre stratégie média.Bien sûr, vous aurez besoin d’un peu de plomberie pour instancier correctement les stratégies algorithmiques dont vous aurez besoin.

C'est un excellent endroit pour utiliser une interface.Nous avons utilisé le modèle Stratégie, qui indique clairement un endroit dans la conception qui va changer.Ainsi, vous devez définir la stratégie comme une interface.Vous devez généralement privilégier les interfaces plutôt que l'héritage lorsque vous souhaitez qu'un objet ait un certain type ;dans ce cas, MediaStrategy.S'appuyer sur l'héritage pour l'identité de type est dangereux ;cela vous enferme dans une hiérarchie d'héritage particulière.Java n'autorise pas l'héritage multiple, vous ne pouvez donc pas étendre quelque chose qui vous donne une implémentation utile ou plus d'identité de type.

Gardez également à l’esprit de ne pas vous laisser emporter par OO (voir le blog) et modélisez toujours les objets en fonction du comportement requis. Si vous conceviez une application où le seul comportement requis était un nom générique et une espèce pour un animal, vous n'auriez besoin que d'une seule classe Animal avec une propriété pour le nom, au lieu de millions de des cours pour tous les animaux possibles du monde.

J'ai une règle empirique approximative

Fonctionnalité: susceptible d'être différent dans toutes les parties :Interface.

Les données et les fonctionnalités seront pour la plupart les mêmes, les parties différentes : classe abstraite.

Données et fonctionnalités réellement fonctionnelles, si elles sont étendues avec de légères modifications : classe ordinaire (béton)

Données et fonctionnalités, aucun changement prévu : classe ordinaire (béton) avec modificateur final.

Données, et peut-être fonctionnalités :lecture seulement: énumérer les membres.

C'est très approximatif et pas du tout strictement défini, mais il existe un spectre allant des interfaces où tout est destiné à être modifié aux énumérations où tout est corrigé un peu comme un fichier en lecture seule.

Les interfaces doivent être petites.Vraiment petit.Si vous décomposez réellement vos objets, vos interfaces ne contiendront probablement que quelques méthodes et propriétés très spécifiques.

Les classes abstraites sont des raccourcis.Y a-t-il des éléments que tous les dérivés de PetBase partagent et que vous pouvez coder une fois et en finir ?Si oui, alors il est temps pour un cours abstrait.

Les classes abstraites sont également limitantes.Bien qu'ils vous offrent un excellent raccourci pour produire des objets enfants, tout objet donné ne peut implémenter qu'une seule classe abstraite.Plusieurs fois, je trouve cela une limitation des classes abstraites, et c'est pourquoi j'utilise beaucoup d'interfaces.

Les classes abstraites peuvent contenir plusieurs interfaces.Votre classe abstraite PetBase peut implémenter IPet (les animaux ont des propriétaires) et IDigestion (les animaux mangent, ou du moins ils devraient).Cependant, PetBase n'implémentera probablement pas IMammal, car tous les animaux de compagnie ne sont pas des mammifères et tous les mammifères ne sont pas des animaux de compagnie.Vous pouvez ajouter un MammalPetBase qui étend PetBase et ajouter IMammal.FishBase pourrait avoir PetBase et ajouter IFish.IFish aurait ISwim et IUnderwaterBreather comme interfaces.

Oui, mon exemple est beaucoup trop compliqué pour un exemple simple, mais cela fait partie de l'avantage de la façon dont les interfaces et les classes abstraites fonctionnent ensemble.

Le cas des classes de base sur les interfaces a été bien expliqué dans les directives de codage du sous-main .NET :

Classes de base vs.Interfaces Un type d'interface est une description partielle d'une valeur, potentiellement prise en charge par de nombreux types d'objets.Utilisez des classes de base au lieu des interfaces chaque fois que possible.Du point de vue du versioning, les classes sont plus flexibles que les interfaces.Avec une classe, vous pouvez expédier la version 1.0, puis dans la version 2.0, ajoutez une nouvelle méthode à la classe.Tant que la méthode n'est pas abstraite, toutes les classes dérivées existantes continuent de fonctionner inchangées.

Étant donné que les interfaces ne prennent pas en charge l'héritage de l'implémentation, le modèle qui s'applique aux classes ne s'applique pas aux interfaces.L'ajout d'une méthode à une interface équivaut à l'ajout d'une méthode abstraite à une classe de base;Toute classe qui implémente l'interface se casse parce que la classe n'implémente pas la nouvelle méthode.Les interfaces sont appropriées dans les situations suivantes:

  1. Plusieurs classes indépendantes souhaitent prendre en charge le protocole.
  2. Ces classes ont déjà des classes de base établies (par exemple, certains sont des contrôles d'interface utilisateur (UI), et certains sont des services Web XML).
  3. Le regroupement n’est ni approprié ni réalisable.Dans toutes les autres situations, l'héritage des classes est un meilleur modèle.

Source: http://jasonroell.com/2014/12/09/interfaces-vs-abstract-classes-what-should-you-use/

C# est un langage merveilleux qui a mûri et évolué au cours des 14 dernières années.C’est formidable pour nous, développeurs, car un langage mature nous offre une multitude de fonctionnalités linguistiques à notre disposition.

Cependant, beaucoup de pouvoir implique beaucoup de responsabilités.Certaines de ces fonctionnalités peuvent être utilisées à mauvais escient, ou il est parfois difficile de comprendre pourquoi vous choisiriez d'utiliser une fonctionnalité plutôt qu'une autre.Au fil des années, une fonctionnalité avec laquelle j'ai vu de nombreux développeurs avoir du mal est de savoir quand choisir d'utiliser une interface ou choisir d'utiliser une classe abstraite.Les deux ont leurs avantages et leurs inconvénients, ainsi que le bon moment et le bon endroit pour les utiliser.Mais comment décider ???

Les deux prévoient la réutilisation de fonctionnalités communes entre les types.La différence la plus évidente est que les interfaces ne fournissent aucune implémentation pour leurs fonctionnalités alors que les classes abstraites vous permettent d'implémenter un comportement « de base » ou « par défaut » et ont ensuite la possibilité de « remplacer » ce comportement par défaut avec les types dérivés des classes si nécessaire. .

Tout cela est bien beau, permet une grande réutilisation du code et adhère au principe DRY (Don't Repeat Yourself) du développement logiciel.Les classes abstraites sont idéales à utiliser lorsque vous avez une relation « est un ».

Par exemple:Un golden retriever « est un » type de chien.Un caniche aussi.Ils peuvent tous les deux aboyer, comme tous les chiens.Cependant, vous voudrez peut-être préciser que le parc à caniches est très différent de l'aboiement du chien « par défaut ».Par conséquent, il pourrait être judicieux que vous implémentiez quelque chose comme suit :

public abstract class Dog
{
      public virtual void Bark()
      {
        Console.WriteLine("Base Class implementation of Bark");
      }
}

public class GoldenRetriever : Dog
{
   // the Bark method is inherited from the Dog class
}

public class Poodle : Dog
{
  // here we are overriding the base functionality of Bark with our new implementation
  // specific to the Poodle class
  public override void Bark()
  {
     Console.WriteLine("Poodle's implementation of Bark");
  }
}

// Add a list of dogs to a collection and call the bark method.

void Main()
{
    var poodle = new Poodle();
    var goldenRetriever = new GoldenRetriever();

    var dogs = new List<Dog>();
    dogs.Add(poodle);
    dogs.Add(goldenRetriever);

    foreach (var dog in dogs)
    {
       dog.Bark();
    }
}

// Output will be:
// Poodle's implementation of Bark
// Base Class implementation of Bark

// 

Comme vous pouvez le voir, ce serait un excellent moyen de garder votre code SEC et de permettre à l'implémentation de la classe de base d'être appelée lorsque l'un des types peut simplement s'appuyer sur le Bark par défaut au lieu d'une implémentation de cas particulier.Les classes comme GoldenRetriever, Boxer, Lab pourraient toutes hériter gratuitement de Bark « par défaut » (classe de basse) simplement parce qu’elles implémentent la classe abstraite Dog.

Mais je suis sûr que vous le saviez déjà.

Vous êtes ici parce que vous voulez comprendre pourquoi vous souhaiterez peut-être choisir une interface plutôt qu'une classe abstraite ou vice versa.Eh bien, une des raisons pour lesquelles vous souhaiterez peut-être choisir une interface plutôt qu'une classe abstraite est lorsque vous n'avez pas ou souhaitez empêcher une implémentation par défaut.Cela est généralement dû au fait que les types qui implémentent l'interface ne sont pas liés dans une relation « est un ».En fait, il n’est pas nécessaire qu’ils soient liés du tout, sauf que chaque type « est capable » ou a « la capacité » de faire ou d’avoir quelque chose.

Maintenant, qu’est-ce que ça veut dire ?Eh bien, par exemple :Un humain n'est pas un canard… et un canard n'est pas un humain.Assez évident.Cependant, un canard et un humain ont tous deux « la capacité » de nager (étant donné que l'humain a réussi ses cours de natation en 1ère année :) ).De plus, puisqu'un canard n'est pas un humain ou vice versa, il ne s'agit pas d'une relation « est un », mais plutôt d'une relation « est capable » et nous pouvons utiliser une interface pour illustrer cela :

// Create ISwimable interface
public interface ISwimable
{
      public void Swim();
}

// Have Human implement ISwimable Interface
public class Human : ISwimable

     public void Swim()
     {
        //Human's implementation of Swim
        Console.WriteLine("I'm a human swimming!");
     }

// Have Duck implement ISwimable interface
public class Duck: ISwimable
{
     public void Swim()
     {
          // Duck's implementation of Swim
          Console.WriteLine("Quack! Quack! I'm a Duck swimming!")
     }
}

//Now they can both be used in places where you just need an object that has the ability "to swim"

public void ShowHowYouSwim(ISwimable somethingThatCanSwim)
{
     somethingThatCanSwim.Swim();
}

public void Main()
{
      var human = new Human();
      var duck = new Duck();

      var listOfThingsThatCanSwim = new List<ISwimable>();

      listOfThingsThatCanSwim.Add(duck);
      listOfThingsThatCanSwim.Add(human);

      foreach (var something in listOfThingsThatCanSwim)
      {
           ShowHowYouSwim(something);
      }
}

 // So at runtime the correct implementation of something.Swim() will be called
 // Output:
 // Quack! Quack! I'm a Duck swimming!
 // I'm a human swimming!

L'utilisation d'interfaces comme le code ci-dessus vous permettra de passer un objet dans une méthode qui « est capable » de faire quelque chose.Le code ne se soucie pas de la façon dont il le fait… Tout ce qu'il sait, c'est qu'il peut appeler la méthode Swim sur cet objet et que cet objet saura quel comportement adopter au moment de l'exécution en fonction de son type.

Encore une fois, cela aide votre code à rester SEC afin que vous n'ayez pas à écrire plusieurs méthodes qui appellent l'objet pour exécuter la même fonction principale (ShowHowHumanSwims (humain), ShowHowDuckSwims (canard), etc.)

L'utilisation d'une interface ici permet aux méthodes appelantes de ne pas avoir à se soucier du type ou de la manière dont le comportement est implémenté.Il sait simplement qu'étant donné l'interface, chaque objet devra avoir implémenté la méthode Swim afin de pouvoir l'appeler en toute sécurité dans son propre code et permettre que le comportement de la méthode Swim soit géré au sein de sa propre classe.

Résumé:

Donc, ma règle principale est d'utiliser une classe abstraite lorsque vous souhaitez implémenter une fonctionnalité « par défaut » pour une hiérarchie de classes ou/et que les classes ou types avec lesquels vous travaillez partagent une relation « est une » (ex.le caniche « est un » type de chien).

D’un autre côté, utilisez une interface lorsque vous n’avez pas de relation « est un » mais que vous avez des types qui partagent « la capacité » de faire ou d’avoir quelque chose (ex.Le canard « n’est pas » un humain.Cependant, le canard et l’humain partagent « la capacité » de nager).

Une autre différence à noter entre les classes abstraites et les interfaces est qu'une classe peut implémenter une ou plusieurs interfaces, mais qu'une classe ne peut hériter que d'UNE classe abstraite (ou de n'importe quelle classe d'ailleurs).Oui, vous pouvez imbriquer des classes et avoir une hiérarchie d'héritage (ce que de nombreux programmes font et devraient avoir), mais vous ne pouvez pas hériter de deux classes dans une définition de classe dérivée (cette règle s'applique à C#.Dans certaines autres langues, vous pouvez le faire, généralement uniquement en raison du manque d'interfaces dans ces langues).

N'oubliez pas non plus, lorsque vous utilisez des interfaces, de respecter le principe de ségrégation des interfaces (ISP).Le FAI déclare qu'aucun client ne devrait être obligé de dépendre de méthodes qu'il n'utilise pas.Pour cette raison, les interfaces doivent être axées sur des tâches spécifiques et sont généralement très petites (ex.IDisposable, IComparable ).

Un autre conseil est que si vous développez de petites fonctionnalités concises, utilisez des interfaces.Si vous concevez de grandes unités fonctionnelles, utilisez une classe abstraite.

J'espère que cela clarifie les choses pour certaines personnes !

De plus, si vous pensez à de meilleurs exemples ou si vous souhaitez souligner quelque chose, faites-le dans les commentaires ci-dessous !

Une différence importante est que vous ne pouvez hériter que un classe de base, mais vous pouvez implémenter beaucoup interfaces.Vous ne souhaitez donc utiliser une classe de base que si vous êtes Absolument certain que vous n'aurez pas besoin d'hériter également d'une classe de base différente.De plus, si vous constatez que votre interface devient volumineuse, vous devriez commencer à chercher à la diviser en quelques éléments logiques qui définissent des fonctionnalités indépendantes, car il n'y a aucune règle selon laquelle votre classe ne peut pas toutes les implémenter (ou que vous pouvez définir une interface différente). interface qui hérite simplement de tous pour les regrouper).

Lorsque j'ai commencé à apprendre la programmation orientée objet, j'ai commis l'erreur facile et probablement courante d'utiliser l'héritage pour partager un comportement commun - même lorsque ce comportement n'était pas essentiel à la nature de l'objet.

Pour s'appuyer davantage sur un exemple très utilisé dans cette question particulière, il existe beaucoup de choses qui peuvent être caressées - copines, voitures, couvertures pelucheuses...- j'aurais donc pu avoir une classe Petable qui fournissait ce comportement commun, et diverses classes en héritant.

Cependant, être pétable ne fait partie de la nature d’aucun de ces objets.Il existe des concepts bien plus importants qui sont essentiel à leur nature - la petite amie est une personne, la voiture est un véhicule terrestre, le chat est un mammifère...

Les comportements doivent d'abord être attribués aux interfaces (y compris l'interface par défaut de la classe) et promus vers une classe de base uniquement s'ils sont (a) communs à un grand groupe de classes qui sont des sous-ensembles d'une classe plus grande - dans le même sens que « chat » et « personne » sont des sous-ensembles de « mammifère ».

Le problème est qu'une fois que vous aurez suffisamment mieux compris la conception orientée objet que moi au début, vous le ferez normalement automatiquement sans même y penser.Ainsi, la pure vérité de la déclaration "coder vers une interface, pas une classe abstraite" devient si évidente que vous avez du mal à croire que quiconque prendrait la peine de la dire - et commencerait à essayer d'y lire d'autres significations.

Une autre chose que j'ajouterais est que si une classe est purement abstrait - sans membres ni méthodes non abstraits et non hérités exposés à l'enfant, au parent ou au client - alors pourquoi est-ce une classe ?Il pourrait être remplacé, dans certains cas par une interface et dans d'autres cas par Null.

Préférez les interfaces aux classes abstraites

Justification, les principaux points à considérer [deux déjà mentionnés ici] sont:

  • Les interfaces sont plus flexibles, car une classe peut implémenter plusieurs interfaces.Étant donné que Java n'a pas d'hérédité multiple, l'utilisation de classes abstraites empêche vos utilisateurs d'utiliser toute autre hiérarchie de classe. En général, préférez les interfaces lorsqu'il n'y a pas d'implémentations par défaut ou d'état. Les collections Java en offrent de bons exemples (carte, ensemble, etc.).
  • Les classes abstraites ont l'avantage de permettre une meilleure compatibilité avant.Une fois que les clients utilisent une interface, vous ne pouvez pas la modifier ;S'ils utilisent une classe abstraite, vous pouvez toujours ajouter un comportement sans casser le code existant. Si la compatibilité est une préoccupation, envisagez d'utiliser des classes abstraites.
  • Même si vous disposez d'implémentations par défaut ou d'un état interne,envisager de proposer une interface et une implémentation abstraite de celle-ci.Cela aidera les clients, mais leur permettra toujours une plus grande liberté si vous le souhaitez [1].
    Bien sûr, le sujet a été longuement discuté ailleurs [2,3].

[1] Cela ajoute bien sûr plus de code, mais si la brièveté est votre principale préoccupation, vous auriez probablement dû éviter Java en premier lieu !

[2] Joshua Bloch, Effective Java, points 16 à 18.

[3] http://www.codeproject.com/KB/ar...

Les commentaires précédents sur l’utilisation de classes abstraites pour une implémentation commune sont définitivement pertinents.Un avantage que je n'ai pas encore vu mentionné est que l'utilisation d'interfaces facilite grandement l'implémentation d'objets fictifs à des fins de tests unitaires.Définir IPet et PetBase comme l'a décrit Jason Cohen vous permet de simuler facilement différentes conditions de données, sans la surcharge d'une base de données physique (jusqu'à ce que vous décidiez qu'il est temps de tester la réalité).

N'utilisez pas de classe de base à moins que vous sachiez ce que cela signifie et qu'elle s'applique dans ce cas.Si cela s’applique, utilisez-le, sinon utilisez des interfaces.Mais notez la réponse concernant les petites interfaces.

L'héritage public est surutilisé dans OOD et exprime bien plus que ce que la plupart des développeurs réalisent ou sont prêts à respecter.Voir le Principe de substituabilité de Liskov

En bref, si A "est un" B alors A n'exige pas plus que B et ne fournit pas moins que B, pour chaque méthode qu'il expose.

Conceptuellement, une interface est utilisée pour définir formellement et semi-formellement un ensemble de méthodes qu'un objet fournira.Formellement signifie un ensemble de noms et de signatures de méthodes, semi-formellement signifie une documentation lisible par l'homme associée à ces méthodes.Les interfaces ne sont que des descriptions d'une API (après tout, API signifie Application Programmer Interface), ils ne peuvent contenir aucune implémentation et il n'est pas possible d'utiliser ou d'exécuter une interface.Ils rendent uniquement explicite le contrat sur la manière dont vous devez interagir avec un objet.

Les classes fournissent une implémentation, elles peuvent déclarer qu'elles implémentent zéro, une ou plusieurs interfaces.Si une classe est destinée à être héritée, la convention est de préfixer le nom de la classe par « Base ».

Il existe une distinction entre une classe de base et une classe de base abstraite (ABC).Les ABC mélangent interface et implémentation.Résumé en dehors de la programmation informatique signifie « résumé », c'est-à-dire « Résumé == Interface ».Une classe de base abstraite peut alors décrire à la fois une interface et une implémentation vide, partielle ou complète destinée à être héritée.

Les opinions sur le moment d'utiliser les interfaces, les classes de base abstraites ou simplement les classes varient énormément en fonction de ce que vous développez et du langage dans lequel vous développez.Les interfaces sont souvent associées uniquement à des langages typés statiquement tels que Java ou C#, mais les langages typés dynamiquement peuvent également avoir des interfaces et des classes de base abstraites.En Python par exemple, la distinction est claire entre une Classe, qui déclare qu'elle met en oeuvre une interface et un objet, qui est une instance d'une classe, et qui est dit fournir cette interface.Il est possible dans un langage dynamique que deux objets, qui sont tous deux des instances de la même classe, puissent déclarer qu'ils fournissent complètement différent interfaces.En Python, cela n'est possible que pour les attributs d'objet, tandis que les méthodes sont des états partagés entre tous les objets d'une classe.Cependant, dans Ruby, les objets peuvent avoir des méthodes par instance, il est donc possible que l'interface entre deux objets de la même classe puisse varier autant que le programmeur le souhaite (cependant, Ruby n'a aucun moyen explicite de déclarer les interfaces).

Dans les langages dynamiques, l'interface avec un objet est souvent implicitement supposée, soit en introspectant un objet et en lui demandant quelles méthodes il fournit (Look Before You Leap), soit de préférence en essayant simplement d'utiliser l'interface souhaitée sur un objet et en détectant les exceptions si l'objet ne fournit pas cette interface (plus facile de demander pardon que la permission).Cela peut conduire à des « faux positifs » où deux interfaces ont le même nom de méthode mais sont sémantiquement différentes. Cependant, le compromis est que votre code est plus flexible puisque vous n'avez pas besoin de trop spécifier à l'avance pour anticiper toutes les utilisations possibles. de votre code.

Une autre option à garder à l'esprit est d'utiliser la relation "has-a", AKA "est implémentée en termes de" ou de "composition". Parfois, c'est un moyen plus propre et plus flexible de structurer les choses que d'utiliser l'héritage "IS-A".

Cela n'a peut-être pas autant de sens logiquement de dire que le chien et le chat "ont" tous les deux un animal de compagnie, mais cela évite les pièges courants de l'héritage multiple :

public class Pet
{
    void Bathe();
    void Train(Trick t);
}

public class Dog
{
    private Pet pet;

    public void Bathe() { pet.Bathe(); }
    public void Train(Trick t) { pet.Train(t); }
}

public class Cat
{
    private Pet pet;

    public void Bathe() { pet.Bathe(); }
    public void Train(Trick t) { pet.Train(t); }
}

Oui, cet exemple montre qu'il y a beaucoup de duplication de code et un manque d'élégance à faire les choses de cette façon.Mais il faut également comprendre que cela permet de garder Chien et Chat découplés de la classe Pet (dans la mesure où Chien et Chat n'ont pas accès aux membres privés de Pet), et cela laisse la possibilité à Chien et Chat d'hériter de quelque chose d'autre. -peut-être la classe Mammifère.

La composition est préférable lorsqu'aucun accès privé n'est requis et que vous n'avez pas besoin de faire référence au chien et au chat à l'aide de références/pointeurs génériques pour animaux de compagnie.Les interfaces vous offrent cette capacité de référence générique et peuvent aider à réduire la verbosité de votre code, mais elles peuvent également brouiller les choses lorsqu'elles sont mal organisées.L'héritage est utile lorsque vous avez besoin d'un accès membre privé, et en l'utilisant, vous vous engagez à coupler fortement vos classes Chien et Chat à votre classe Animal de compagnie, ce qui représente un coût élevé à payer.

Entre l’héritage, la composition et les interfaces, il n’existe pas de solution unique, et il est utile de réfléchir à la manière dont les trois options peuvent être utilisées en harmonie.Parmi les trois, l’héritage est généralement l’option qui devrait être la moins souvent utilisée.

Cela dépend de vos besoins.Si IPet est assez simple, je préférerais l'implémenter.Sinon, si PetBase implémente une tonne de fonctionnalités que vous ne souhaitez pas dupliquer, alors allez-y.

L'inconvénient de l'implémentation d'une classe de base est l'obligation de override (ou new) méthodes existantes.Cela en fait des méthodes virtuelles, ce qui signifie que vous devez faire attention à la façon dont vous utilisez l'instance d'objet.

Enfin, l'héritage unique de .NET me tue.Un exemple naïf :Disons que vous créez un contrôle utilisateur, vous héritez donc UserControl.Mais maintenant, vous ne pouvez pas non plus hériter PetBase.Cela vous oblige à vous réorganiser, par exemple à créer un PetBase membre de la classe, à la place.

@Joël :Certains langages (par exemple C++) autorisent l'héritage multiple.

En général, je n’implémente ni l’un ni l’autre jusqu’à ce que j’en ai besoin.Je privilégie les interfaces aux classes abstraites car cela donne un peu plus de flexibilité.S'il existe un comportement commun dans certaines des classes héritantes, je le déplace vers le haut et crée une classe de base abstraite.Je ne vois pas la nécessité des deux, car ils servent essentiellement le même objectif, et avoir les deux est une mauvaise odeur de code (à mon humble avis) que la solution a été trop conçue.

Concernant C#, dans un certain sens, les interfaces et les classes abstraites peuvent être interchangeables.Cependant, les différences sont :i) les interfaces ne peuvent pas implémenter de code ;ii) pour cette raison, les interfaces ne peuvent pas appeler la sous-classe plus haut dans la pile ;et iii) seule une classe abstraite peut être héritée sur une classe, alors que plusieurs interfaces peuvent être implémentées sur une classe.

Par définition, l'interface fournit une couche pour communiquer avec un autre code.Toutes les propriétés et méthodes publiques d’une classe implémentent par défaut une interface implicite.Nous pouvons également définir une interface comme un rôle, chaque fois qu'une classe doit jouer ce rôle, elle doit l'implémenter en lui donnant différentes formes d'implémentation en fonction de la classe qui l'implémente.Ainsi, lorsque vous parlez d'interface, vous parlez de polymorphisme et lorsque vous parlez de classe de base, vous parlez d'héritage.Deux concepts de oups !!!

J'ai constaté qu'un modèle Interface > Abstract > Concrete fonctionne dans le cas d'utilisation suivant :

1.  You have a general interface (eg IPet)
2.  You have a implementation that is less general (eg Mammal)
3.  You have many concrete members (eg Cat, Dog, Ape)

La classe abstraite définit les attributs partagés par défaut des classes concrètes, tout en appliquant l'interface.Par exemple:

public interface IPet{

    public boolean hasHair();

    public boolean walksUprights();

    public boolean hasNipples();
}

Maintenant, puisque tous les mammifères ont des cheveux et des mamelons (AFAIK, je ne suis pas zoologiste), nous pouvons intégrer cela dans la classe de base abstraite.

public abstract class Mammal() implements IPet{

     @override
     public walksUpright(){
         throw new NotSupportedException("Walks Upright not implemented");
     }

     @override
     public hasNipples(){return true}

     @override
     public hasHair(){return true}

Et puis les classes concrètes définissent simplement qu'elles marchent debout.

public class Ape extends Mammal(){

    @override
    public walksUpright(return true)
}

public class Catextends Mammal(){

    @override
    public walksUpright(return false)
}

Cette conception est intéressante lorsqu'il y a beaucoup de classes concrètes et que vous ne voulez pas conserver un passe-partout uniquement pour programmer sur une interface.Si de nouvelles méthodes étaient ajoutées à l’interface, cela briserait toutes les classes résultantes, vous bénéficiez donc toujours des avantages de l’approche par interface.

Dans ce cas, l’abstrait pourrait tout aussi bien être concret ;cependant, la désignation abstraite contribue à souligner que ce modèle est utilisé.

Un héritier d'une classe de base doit avoir une relation "est un".L'interface représente une relation "implémente une".N'utilisez donc une classe de base que lorsque vos héritiers maintiendront la relation.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top