web-dev-qa-db-fra.com

Interfaces C # - Quel est le problème?

La raison des interfaces m'échappe vraiment. D'après ce que j'ai compris, il s'agit en quelque sorte d'une solution de contournement pour le multi-héritage inexistant qui n'existe pas en C # (c'est ce que l'on m'a dit).

Tout ce que je vois, c’est que vous prédéfinissez certains membres et fonctions, qui doivent ensuite être redéfinis dans la classe. Rendre ainsi l'interface redondante. Cela ressemble à de la syntaxe ... eh bien, de la malbouffe pour moi (S'il vous plaît, ne vous offusquez pas. Vraiment mal comme une chose inutile).

Dans l'exemple ci-dessous tiré d'un thread d'interfaces C # différent sur le débordement de pile, je créerais simplement une classe de base appelée Pizza au lieu d'une interface.

exemple simple (tiré d'une contribution de dépassement de pile différente)

public interface IPizza
{
    public void Order();

}

public class PepperoniPizza : IPizza
{
    public void Order()
    {
        //Order Pepperoni pizza
    }
}

public class HawaiiPizza : IPizza
{
    public void Order()
    {
        //Order HawaiiPizza
    }
}
226
Nebelhom

Le fait est que l'interface représente un contrat . Un ensemble de méthodes publiques que toute classe d'implémentation doit avoir. Techniquement, l’interface ne gouverne que la syntaxe, c’est-à-dire quelles méthodes existent, quels arguments sont récupérés et ce qu’ils renvoient. Habituellement, ils encapsulent également la sémantique, bien que cela ne soit que par la documentation.

Vous pouvez ensuite avoir différentes implémentations d'une interface et les échanger à volonté. Dans votre exemple, chaque instance de pizza étant une IPizza, vous pouvez utiliser IPizza chaque fois que vous gérez une instance d'un type de pizza inconnu. Toute instance dont le type hérite de IPizza a la garantie d'être commandable, car elle possède une méthode Order().

Python n'est pas typé de manière statique. Par conséquent, les types sont conservés et recherchés au moment de l'exécution. Vous pouvez donc essayer d'appeler une méthode Order() sur n'importe quel objet. Le moteur d'exécution est heureux tant que l'objet a une telle méthode et qu'il hausse probablement les épaules et dit «Meh» si ce n'est pas le cas. Pas si en C #. Le compilateur est responsable de faire les appels corrects et s'il ne dispose que d'une variable aléatoire object, il ne sait pas encore si l'instance au moment de l'exécution utilisera cette méthode. Du point de vue du compilateur, il n'est pas valide puisqu'il ne peut pas le vérifier. (Vous pouvez faire de telles choses avec réflexion ou avec le mot clé dynamic, mais cela va un peu loin maintenant, je suppose.)

Notez également qu’une interface dans le sens habituel ne doit pas nécessairement être une C # interface, elle peut également être une classe abstraite ou même une classe normale Cependant, interface suffit).

167
Joey

Personne n'a vraiment expliqué en termes simples comment les interfaces sont utiles, je vais donc essayer (et voler une idée dans la réponse de Shamim). 

Prenons l’idée d’un service de commande de pizzas. Vous pouvez avoir plusieurs types de pizzas et une action commune pour chaque pizza prépare la commande dans le système. Chaque pizza doit être préparé mais chaque pizza est préparé différemment. Par exemple, lors de la commande d'une pizza à croûte farcie, le système doit probablement vérifier que certains ingrédients sont disponibles au restaurant et mettre de côté ceux qui ne sont pas nécessaires pour les pizzas à fond plat.

En écrivant cela dans le code, techniquement, vous pouvez simplement faire

public class Pizza()
{
    public void Prepare(PizzaType tp)
    {
        switch (tp)
        {
            case PizzaType.StuffedCrust:
                // prepare stuffed crust ingredients in system
                break;

            case PizzaType.DeepDish:
                // prepare deep dish ingredients in system
                break;

            //.... etc.
        }
    }
}

Cependant, les pizzas profondes (en termes de C #) peuvent nécessiter que la méthode Prepare() définisse des propriétés différentes de celles de la croûte farcie. Vous obtenez donc de nombreuses propriétés facultatives et la classe ne s’échelonne pas bien nouveaux types de pizza).

La bonne façon de résoudre ce problème consiste à utiliser l'interface. L'interface déclare que toutes les pizzas peuvent être préparées, mais que chaque pizza peut être préparée différemment. Donc, si vous avez les interfaces suivantes:

public interface IPizza
{
    void Prepare();
}

public class StuffedCrustPizza : IPizza
{
    public void Prepare()
    {
        // Set settings in system for stuffed crust preparations
    }
}

public class DeepDishPizza : IPizza
{
    public void Prepare()
    {
        // Set settings in system for deep dish preparations
    }
}

Désormais, votre code de traitement de la commande n'a pas besoin de savoir exactement quels types de pizzas ont été commandés pour pouvoir manipuler les ingrédients. Il a juste:

public PreparePizzas(IList<IPizza> pizzas)
{
    foreach (IPizza pizza in pizzas)
        pizza.Prepare();
}

Même si chaque type de pizza est préparé différemment, cette partie du code ne doit pas se préoccuper du type de pizza à traiter; elle sait simplement qu'elle est appelée pour les pizzas et donc chaque appel à Prepare préparera automatiquement chaque pizza. correctement basé sur son type, même si la collection contient plusieurs types de pizzas.

409
KallDrexx

Pour moi, l’intérêt de ces éléments n’est devenu clair que lorsque vous cessez de les considérer comme des moyens de rendre votre code plus facile/plus rapide à écrire - ce n’est pas leur objectif. Ils ont un certain nombre d'utilisations:

(Cela va perdre l'analogie pizza, car il n'est pas très facile de visualiser une utilisation de cela)

Supposons que vous créez un jeu simple à l'écran et que vous aurez des créatures avec lesquelles vous interagirez.

A: Ils peuvent rendre votre code plus facile à gérer à l'avenir en introduisant un couplage lâche entre votre implémentation front-end et votre back-end. 

Vous pouvez écrire ceci pour commencer, car il n'y aura que des trolls:

// This is our back-end implementation of a troll
class Troll
{
    void Walk(int distance)
    {
        //Implementation here
    }
}

L'extrémité avant:

function SpawnCreature()
{
    Troll aTroll = new Troll();

    aTroll.Walk(1);
}

Deux semaines plus tard, le marketing décide que vous avez également besoin d'orques, car ils en parlent sur Twitter, vous devez donc faire quelque chose comme:

class Orc
{
    void Walk(int distance)
    {
        //Implementation (orcs are faster than trolls)
    }
}

L'extrémité avant:

void SpawnCreature(creatureType)
{
    switch(creatureType)
    {
         case Orc:

           Orc anOrc = new Orc();
           anORc.Walk();

          case Troll:

            Troll aTroll = new Troll();
             aTroll.Walk();
    }
}

Et vous pouvez voir comment cela commence à devenir compliqué. Vous pouvez utiliser une interface ici pour que votre frontal soit écrit une fois et (voici l'essentiel) testé, et vous pouvez ensuite connecter d'autres éléments dorsaux selon les besoins:

interface ICreature
{
    void Walk(int distance)
}

public class Troll : ICreature
public class Orc : ICreature 

//etc

Front end est alors:

void SpawnCreature(creatureType)
{
    ICreature creature;

    switch(creatureType)
    {
         case Orc:

           creature = new Orc();

          case Troll:

            creature = new Troll();
    }

    creature.Walk();
}

Le front-end ne se soucie plus que de l'interface ICreature - pas de l'implémentation interne d'un troll ou d'un orc, mais seulement du fait qu'ils implémentent ICreature.

Un point important à noter lorsque vous examinez ceci de ce point de vue est que vous auriez aussi facilement pu utiliser une classe de créatures abstraites et que, de ce point de vue, cela a le même effet que

Et vous pouvez extraire la création dans une usine:

public class CreatureFactory {

 public ICreature GetCreature(creatureType)
 {
    ICreature creature;

    switch(creatureType)
    {
         case Orc:

           creature = new Orc();

          case Troll:

            creature = new Troll();
    }

    return creature;
  }
}

Et notre front-end deviendrait alors:

CreatureFactory _factory;

void SpawnCreature(creatureType)
{
    ICreature creature = _factory.GetCreature(creatureType);

    creature.Walk();
}

À présent, le frontal n'a même plus besoin de faire référence à la bibliothèque où Troll et Orc sont implémentés (à condition que l'usine se trouve dans une bibliothèque séparée) - il n'a pas besoin de savoir quoi que ce soit à leur sujet.

B: Disons que vous avez une fonctionnalité que seules certaines créatures auront dans votre structure de données par ailleurs homogène, par exemple.

interface ICanTurnToStone
{
   void TurnToStone();
}

public class Troll: ICreature, ICanTurnToStone

Front end pourrait alors être:

void SpawnCreatureInSunlight(creatureType)
{
    ICreature creature;

    switch(creatureType)
    {
         case Orc:

           creature = new Orc();

          case Troll:

            creature = new Troll();
    }

    creature.Walk();

    if (creature is ICanTurnToStone)
    {
       (ICanTurnToStone)creature.TurnToStone();
    }
}

C: Utilisation pour l'injection de dépendance

Il est plus facile de travailler avec la plupart des infrastructures d'injection de dépendance lorsqu'il existe un couplage très lâche entre le code frontal et l'implémentation principale. Si nous prenons notre exemple d'usine ci-dessus et que notre usine implémente une interface:

public interface ICreatureFactory {
     ICreature GetCreature(string creatureType);
}

Ceci pourrait alors être injecté par notre frontal (par exemple un contrôleur API MVC) via le constructeur (généralement):

public class CreatureController : Controller {

   private readonly ICreatureFactory _factory;

   public CreatureController(ICreatureFactory factory) {
     _factory = factory;
   }

   public HttpResponseMessage TurnToStone(string creatureType) {

       ICreature creature = _factory.GetCreature(creatureType);

       creature.TurnToStone();

       return Request.CreateResponse(HttpStatusCode.OK);
   }
}

Avec notre infrastructure DI (par exemple, Ninject ou Autofac), nous pouvons les configurer de sorte qu’au moment de l’exécution, une instance de CreatureFactory soit créée chaque fois qu’une ICreatureFactory est requise dans un constructeur - notre code est simple et agréable. 

Cela signifie également que lorsque nous écrivons un test unitaire pour notre contrôleur, nous pouvons fournir une ICreatureFactory simulée (par exemple, si l'implémentation concrète nécessite un accès à la base de données, nous ne voulons pas que nos tests unitaires dépendent de cela) et tester facilement le code dans notre contrôleur. .

D: Il y a d'autres utilisations, par exemple vous avez deux projets A et B qui, pour des raisons "héritées", ne sont pas bien structurés et A fait référence à B. 

Vous trouvez ensuite une fonctionnalité dans B qui doit appeler une méthode déjà en A. Vous ne pouvez pas le faire en utilisant des implémentations concrètes car vous obtenez une référence circulaire.

Vous pouvez avoir une interface déclarée en B que la classe de A implémente. Votre méthode en B peut recevoir une instance d'une classe qui implémente l'interface sans problème, même si l'objet concret est d'un type en A.

99
Paddy

Voici vos exemples ré-expliqués:

public interface IFood // not Pizza
{
    public void Prepare();

}

public class Pizza : IFood
{
    public void Prepare() // Not order for explanations sake
    {
        //Prepare Pizza
    }
}

public class Burger : IFood
{
    public void Prepare()
    {
        //Prepare Burger
    }
}
31
Shamim Hafiz

Les exemples ci-dessus n'ont pas beaucoup de sens. Vous pouvez accomplir tous les exemples ci-dessus en utilisant des classes (classe abstraite si vous voulez qu'elle se comporte uniquement comme un contrat ):

public abstract class Food {
    public abstract void Prepare();
}

public class Pizza : Food  {
    public override void Prepare() { /* Prepare pizza */ }
}

public class Burger : Food  {
    public override void Prepare() { /* Prepare Burger */ }
}

Vous obtenez le même comportement qu'avec l'interface. Vous pouvez créer un List<Food> et effectuer une itération sans savoir quelle classe se trouve en haut.

Un exemple plus adéquat serait un héritage multiple:

public abstract class MenuItem {
    public string Name { get; set; }
    public abstract void BringToTable();
}

// Notice Soda only inherits from MenuItem
public class Soda : MenuItem {
    public override void BringToTable() { /* Bring soda to table */ }
}


// All food needs to be cooked (real food) so we add this
// feature to all food menu items
public interface IFood {
    void Cook();
}

public class Pizza : MenuItem, IFood {
    public override void BringToTable() { /* Bring pizza to table */ }
    public void Cook() { /* Cook Pizza */ }
}

public class Burger : MenuItem, IFood {
    public override void BringToTable() { /* Bring burger to table */ }
    public void Cook() { /* Cook Burger */ }
}

Ensuite, vous pouvez tous les utiliser en tant que MenuItem et ne vous souciez pas de la façon dont ils gèrent chaque appel de méthode.

public class Waiter {
    public void TakeOrder(IEnumerable<MenuItem> order) 
    {
        // Cook first
        // (all except soda because soda is not IFood)
        foreach (var food in order.OfType<IFood>())
            food.Cook();

        // Bring them all to the table
        // (everything, including soda, pizza and burger because they're all menu items)
        foreach (var menuItem in order)
            menuItem.BringToTable();
    }
}
20
Aleksandar Toplek

En l'absence de duc duc comme vous pouvez l'utiliser en Python, C # s'appuie sur des interfaces pour fournir des abstractions. Si les dépendances d'une classe étaient tous de types concrets, vous ne pourriez pas en transmettre un autre - utilisez des interfaces pour n'importe quel type mettant en œuvre l'interface.

11
BrokenGlass

L'exemple Pizza est mauvais, car vous devriez utiliser une classe abstraite qui gère la commande, et les pizzas doivent simplement remplacer le type de pizza, par exemple.

Vous utilisez des interfaces lorsque vous avez une propriété partagée, mais que vos classes héritent de différents endroits ou que vous ne pouvez utiliser aucun code commun. Par exemple, il s’agit de choses qui peuvent être éliminées IDisposable, vous savez qu’elles le seront, vous ne savez tout simplement pas ce qui se passera lorsqu’elles seront éliminées.

Une interface est simplement un contrat qui vous indique certaines choses qu'un objet peut faire, quels paramètres et quels types de retour sont attendus.

11
bevacqua

Considérez le cas où vous ne contrôlez ni ne possédez les classes de base.

Prenez les contrôles visuels, par exemple, dans .NET pour Winforms, ils héritent tous de la classe de base Control, qui est complètement définie dans le framework .NET.

Supposons que vous créez des contrôles personnalisés. Vous souhaitez créer de nouveaux boutons, zones de texte, listes de consultation, grilles, etc., et vous souhaitez qu’ils disposent tous de certaines fonctionnalités propres à votre ensemble de contrôles.

Par exemple, vous voudrez peut-être un moyen commun de gérer le thème ou un moyen courant de gérer la localisation.

Dans ce cas, vous ne pouvez pas "simplement créer une classe de base" car si vous faites cela, vous devez réimplémenter tout qui concerne les contrôles.

Au lieu de cela, vous descendez de Button, TextBox, ListView, GridView, etc. et ajoutez votre code.

Mais cela pose un problème: comment identifier les contrôles qui sont "à vous", comment construire du code indiquant "pour tous les contrôles du formulaire qui sont à moi, définissez le thème sur X".

Entrez les interfaces.

Les interfaces sont un moyen de regarder un objet, de déterminer que cet objet adhère à un certain contrat.

Vous créez "YourButton", descendez de Button et ajoutez un support pour toutes les interfaces dont vous avez besoin.

Cela vous permettrait d'écrire du code comme suit:

foreach (Control ctrl in Controls)
{
    if (ctrl is IMyThemableControl)
        ((IMyThemableControl)ctrl).SetTheme(newTheme);
}

Cela ne serait pas possible sans interfaces, vous devriez écrire un code comme celui-ci:

foreach (Control ctrl in Controls)
{
    if (ctrl is MyThemableButton)
        ((MyThemableButton)ctrl).SetTheme(newTheme);
    else if (ctrl is MyThemableTextBox)
        ((MyThemableTextBox)ctrl).SetTheme(newTheme);
    else if (ctrl is MyThemableGridView)
        ((MyThemableGridView)ctrl).SetTheme(newTheme);
    else ....
}
10

Dans ce cas, vous pourriez (et probablement le feriez) simplement définir une classe de base Pizza et en hériter. Cependant, il existe deux raisons pour lesquelles les interfaces vous permettent de réaliser des tâches impossibles à atteindre:

  1. Une classe peut implémenter plusieurs interfaces. Il définit simplement les fonctionnalités que la classe doit posséder. L'implémentation d'une gamme d'interfaces signifie qu'une classe peut remplir plusieurs fonctions à différents endroits.

  2. Une interface peut être définie dans une portée plus lourde que la classe ou l'appelant. Cela signifie que vous pouvez séparer la fonctionnalité, séparer la dépendance du projet et conserver la fonctionnalité dans un projet ou une classe, et la mettre en œuvre ailleurs.

Une implication de 2 est que vous pouvez modifier la classe utilisée, en exigeant simplement qu'elle implémente l'interface appropriée.

7
Schroedingers Cat

Pensez que vous ne pouvez pas utiliser l'héritage multiple en C #, puis relisez votre question. 

6
Claus Jørgensen

Interface = contrat, utilisé pour couplage lâche (voir SAISIR ). 

5
Kirill Polishchuk

Une interface est en réalité un contrat que les classes d'implémentation doivent respecter. C'est en fait la base de pratiquement tous les modèles de conception que je connais.

Dans votre exemple, l'interface est créée car tout ce qui est IS A Pizza, ce qui signifie implémenter l'interface Pizza, est garanti d'avoir implémenté 

public void Order();

Après votre code mentionné, vous pourriez avoir quelque chose comme ça:

public void orderMyPizza(IPizza myPizza) {
//This will always work, because everyone MUST implement order
      myPizza.order();
}

De cette façon, vous utilisez le polymorphisme et tout ce qui vous préoccupe est que vos objets répondent à order ().

4
Oscar Gomez

Si je travaille sur une API pour dessiner des formes, je peux utiliser DirectX ou des appels graphiques, ou OpenGL. Je vais donc créer une interface qui va abstraire ma mise en oeuvre de ce que vous appelez.

Donc, vous appelez une méthode factory: MyInterface i = MyGraphics.getInstance(). Ensuite, vous avez un contrat, vous savez donc à quoi vous pouvez vous attendre dans MyInterface. Vous pouvez donc appeler i.drawRectangle ou i.drawCube et savoir que, si vous permutez une bibliothèque par une autre, les fonctions sont prises en charge.

Cela devient plus important si vous utilisez Dependency Injection, car vous pouvez alors, dans un fichier XML, permuter les implémentations.

Donc, vous pouvez avoir une bibliothèque de chiffrement pouvant être exportée pour un usage général, et une autre pour la vente uniquement à des sociétés américaines. La différence réside dans le fait que vous modifiez un fichier de configuration et que le reste du programme ne le fait pas. modifié.

Ceci est très utilisé avec les collections dans .NET, car vous devriez simplement utiliser, par exemple, les variables List, et ne vous inquiétez pas s'il s'agissait d'une ArrayList ou d'une LinkedList.

Tant que vous codez sur l'interface, le développeur peut modifier l'implémentation réelle et le reste du programme reste inchangé.

Ceci est également utile lors des tests unitaires, car vous pouvez simuler des interfaces entières. Je n'ai donc pas besoin d'aller dans une base de données, mais dans une implémentation simulée qui ne renvoie que des données statiques. Je peux donc tester ma méthode sans me soucier de savoir si la base de données est en maintenance ou non.

4
James Black

J'ai fait une recherche du mot "composition" sur cette page et je ne l'ai pas vue une fois. Cette réponse s’ajoute bien aux réponses susmentionnées.

L'une des raisons absolument essentielles de l'utilisation d'interfaces dans un projet orienté objet est qu'elles vous permettent de privilégier la composition par rapport à l'héritage. En implémentant des interfaces, vous pouvez dissocier vos implémentations des différents algorithmes que vous leur appliquez.

Ce superbe didacticiel "Motif de décorateur" de Derek Banas (qui - assez curieusement - utilise aussi la pizza comme exemple) est une illustration qui en vaut la peine:

https://www.youtube.com/watch?v=j40kRwSm4VE

3
Jay Edwards

Je suis surpris que peu d'articles contiennent la raison la plus importante d'une interface: Design Patterns . C’est l’ensemble du problème dans l’utilisation des contrats, et bien qu’il s’agisse d’une décoration syntaxique du code machine (pour être honnête, le compilateur les ignore probablement), l’abstraction et les interfaces sont essentielles pour la POO, la compréhension humaine et les architectures de systèmes complexes.

Élargissons l’analogie de la pizza pour dire un repas complet à 3 plats. Nous aurons toujours le noyau Prepare() pour toutes nos catégories d’aliments, mais nous aurions aussi des déclarations abstraites pour les sélections de cours (entrée, plat principal, dessert) et des propriétés différentes pour les types d’aliments (salé/doux, végétarien/non végétarien). , sans gluten, etc.).

Sur la base de ces spécifications, nous pourrions implémenter le modèle Abstract Factory pour conceptualiser l’ensemble du processus, mais utiliser des interfaces pour garantir que seules les fondations sont en béton. Tout le reste pourrait devenir flexible ou encourager le polymorphisme, tout en maintenant l'encapsulation entre les différentes classes de Course qui implémentent l'interface ICourse

Si j'avais plus de temps, j'aimerais élaborer un exemple complet à ce sujet, ou quelqu'un peut me le proposer, mais en résumé, une interface C # serait le meilleur outil pour concevoir ce type de système.

2
ShaunnyBwoy

Voici une interface pour les objets ayant une forme rectangulaire:

interface IRectangular
{
    Int32 Width();
    Int32 Height();
}

Il vous suffit de mettre en place des moyens d'accéder à la largeur et à la hauteur de l'objet.

Définissons maintenant une méthode qui fonctionnera sur tout objet de type IRectangular:

static class Utils
{
    public static Int32 Area(IRectangular rect)
    {
        return rect.Width() * rect.Height();
    }
}

Cela retournera la surface de tout objet rectangulaire.

Implémentons une classe SwimmingPool de forme rectangulaire:

class SwimmingPool : IRectangular
{
    int width;
    int height;

    public SwimmingPool(int w, int h)
    { width = w; height = h; }

    public int Width() { return width; }
    public int Height() { return height; }
}

Et une autre classe House qui est aussi rectangulaire:

class House : IRectangular
{
    int width;
    int height;

    public House(int w, int h)
    { width = w; height = h; }

    public int Width() { return width; }
    public int Height() { return height; }
}

Dans ce cas, vous pouvez appeler la méthode Area sur les maisons ou les piscines:

var house = new House(2, 3);

var pool = new SwimmingPool(3, 4);

Console.WriteLine(Utils.Area(house));
Console.WriteLine(Utils.Area(pool));

De cette manière, vos classes peuvent "hériter" du comportement (méthodes statiques) de n'importe quel nombre d'interfaces.

2
dharmatech

Les interfaces servent à appliquer une connexion entre différentes classes. par exemple, vous avez une classe pour la voiture et un arbre;

public class Car { ... }

public class Tree { ... }

vous souhaitez ajouter une fonctionnalité gravable pour les deux classes. Mais chaque classe a ses propres moyens de brûler. alors vous faites simplement;

public class Car : IBurnable
{
public void Burn() { ... }
}

public class Tree : IBurnable
{
public void Burn() { ... }
}
2
hakan

Une interface définit un contrat entre le fournisseur d’une certaine fonctionnalité et les consommateurs correspondants. Il sépare l'implémentation du contrat (interface). Vous devriez jeter un coup d'œil à l'architecture et au design orientés objet. Vous voudrez peut-être commencer par wikipedia: http://en.wikipedia.org/wiki/Interface_(computing)

2
home

Vous obtiendrez des interfaces, quand vous en aurez besoin :) Vous pouvez étudier des exemples, mais vous avez besoin du logiciel Aha! effet pour vraiment les obtenir.

Maintenant que vous savez ce que sont les interfaces, codez-les sans elles. Tôt ou tard, vous rencontrerez un problème où l'utilisation d'interfaces sera la chose la plus naturelle à faire.

2
sventevit

Il y a beaucoup de bonnes réponses ici, mais je voudrais essayer d'un point de vue légèrement différent.

Vous connaissez peut-être les principes SOLID de la conception orientée objet. En résumé:

S - Principe de responsabilité unique O - Principe d'ouverture/fermeture L - Principe de substitution de Liskov I - Principe de séparation des interfaces D - Principe d'inversion de dépendance

Suivre les principes SOLID permet de produire un code propre, bien factorisé, cohérent et faiblement couplé. Étant donné que:

"La gestion des dépendances est le principal défi des logiciels à toutes les échelles" (Donald Knuth)

alors tout ce qui aide à la gestion de la dépendance est une grande victoire. Les interfaces et le principe d'inversion des dépendances aident vraiment à découpler le code des dépendances sur des classes concrètes, de sorte que le code puisse être écrit et raisonné en termes de comportements plutôt que d'implémentations. Cela permet de diviser le code en composants pouvant être composés au moment de l'exécution plutôt que de les compiler et signifie également que ces composants peuvent être facilement insérés et branchés sans avoir à modifier le reste du code.

Les interfaces aident en particulier avec le principe d'inversion de dépendance, dans lequel le code peut être intégré à un ensemble de services, chaque service étant décrit par une interface. Les services peuvent ensuite être "injectés" dans les classes lors de l'exécution en les transmettant en tant que paramètre constructeur. Cette technique devient vraiment essentielle si vous commencez à écrire des tests unitaires et utilisez un développement piloté par les tests. Essayez le! Vous comprendrez rapidement comment les interfaces permettent de diviser le code en fragments gérables pouvant être testés individuellement de manière isolée.

1
Tim Long

La manière la plus simple de penser aux interfaces est de reconnaître ce que signifie héritage. Si la classe CC hérite de la classe C, cela signifie à la fois que:

  1. La classe CC peut utiliser n’importe quel membre public ou protégé de la classe C comme s’ils étaient les siens. Il suffit donc d’implémenter des éléments qui n’existent pas dans la classe parente .
  2. Une référence à un CC peut être transmise ou assignée à une routine ou à une variable qui attend une référence à un C .

Ces deux fonctions d’héritage sont en quelque sorte indépendantes; bien que l'héritage applique les deux simultanément, il est également possible d'appliquer le second sans le premier. Cela est utile car permettre à un objet d'hériter des membres de deux ou plusieurs classes non liées est beaucoup plus compliqué que de permettre à un type d'objet d'être substituable à plusieurs types.

Une interface est un peu comme une classe de base abstraite, mais avec une différence essentielle: un objet qui hérite d'une classe de base ne peut hériter d'aucune autre classe. En revanche, un objet peut implémenter une interface sans affecter sa capacité à hériter de la classe souhaitée ou à implémenter d'autres interfaces.

Une caractéristique intéressante de ce système (sous-utilisé dans le framework .net, à mon humble avis) est qu’elles permettent d’indiquer de manière déclarative ce que peut faire un objet. Certains objets, par exemple, voudront un objet source de données à partir duquel ils pourront extraire des éléments par index (comme cela est possible avec une liste), mais ils n’auront pas besoin de stocker quoi que ce soit à cet endroit. D'autres routines auront besoin d'un objet de dépôt de données où elles peuvent stocker des éléments non par index (comme avec Collection.Add), mais elles n'auront pas besoin de lire quoi que ce soit. Certains types de données autoriseront l’accès par index, mais ne permettront pas l’écriture; d'autres autoriseront l'écriture, mais ne permettront pas l'accès par index. Certains, bien sûr, autoriseront les deux.

Si ReadableByIndex et Appendable étaient des classes de base non liées, il serait impossible de définir un type pouvant être transmis à la fois à des éléments qui attendent un ReadableByIndex et à des éléments qui sont attendus à un appendable. On pourrait essayer d'atténuer cela en faisant en sorte que ReadableByIndex ou Appendable dérive de l'autre; la classe dérivée devrait mettre à disposition des membres du public pour les deux objectifs, mais avertissez que certains membres du public pourraient ne pas fonctionner. Certaines classes et interfaces de Microsoft le font, mais c'est plutôt dégoûtant. Une approche plus propre consiste à disposer d'interfaces aux fins différentes, puis à faire en sorte que les objets implémentent des interfaces pour les tâches qu'ils peuvent réellement effectuer. Si l'une d'entre elles avait une interface IReadableByIndex et une autre interface IAppendable, les classes pouvant effectuer l'une ou l'autre pourraient implémenter les interfaces appropriées pour les tâches qu'elles peuvent effectuer.

0
supercat

Pour moi, l’avantage/le bénéfice d’une interface est qu’elle est plus flexible qu’une classe abstraite. Étant donné que vous ne pouvez hériter que d'une classe abstraite mais que vous pouvez implémenter plusieurs interfaces, les modifications apportées à un système qui hérite d'une classe abstraite à plusieurs endroits deviennent problématiques. Si elle est héritée à 100 endroits, une modification nécessite des modifications à toutes les 100. Cependant, avec l'interface, vous pouvez placer la nouvelle modification dans une nouvelle interface et simplement utiliser cette interface à sa place (Interface Seq. De SOLID). En outre, l'utilisation de la mémoire semble être moindre avec l'interface car un objet de l'exemple d'interface n'est utilisé qu'une seule fois en mémoire, quel que soit le nombre d'emplacements implémentant l'interface.

0
ScottS

Thérèse demande vraiment de bons exemples.

En outre, dans le cas d’une instruction switch, vous n’avez plus besoin de gérer et de changer chaque fois que vous souhaitez exécuter une tâche d’une manière spécifique. 

Dans votre exemple de pizza, si vous voulez faire une pizza, l'interface est tout ce dont vous avez besoin, à partir de là, chaque pizza prend en charge sa propre logique. 

Cela aide à réduire le couplage et la complexité cyclomatique. Vous devez toujours mettre en œuvre la logique, mais vous devrez en garder moins dans l’ensemble. 

Pour chaque pizza, vous pouvez alors garder une trace des informations spécifiques à cette pizza. Ce que les autres pizzas ont n'a pas d'importance, car seules les autres pizzas doivent savoir.

0
tam

L'objectif principal des interfaces est de créer un contrat entre vous et toute autre classe implémentant cette interface, ce qui rend votre code découplé et permet son évolutivité. 

0
Samir Adel

Les interfaces peuvent également être chaînées pour créer une autre interface. Cette possibilité d'implémentation de plusieurs interfaces donne au développeur l'avantage d'ajouter des fonctionnalités à ses classes sans avoir à modifier les fonctionnalités de classe actuelles (principes SOLID). 

O = "Les classes doivent être ouvertes pour extension mais fermées pour modification"

0
user2655145