330 votes

Interfaces - A quoi bon ?

La raison des interfaces m'échappe vraiment. D'après ce que j'ai compris, il s'agit d'une sorte de contournement de la multi-héritage qui n'existe pas en C# (du moins, c'est ce qu'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. Ce qui rend l'interface redondante. J'ai l'impression que c'est de la camelote syntaxique (sans vouloir vous offenser, je vous en prie).

Dans l'exemple ci-dessous, tiré d'un autre fil de discussion sur les interfaces C# sur stack overflow, je créerais simplement une classe de base appelée Pizza au lieu d'une interface.

exemple simple (tiré d'une autre contribution à stack overflow)

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
    }
}

0 votes

J'ai l'impression qu'il y a des doublons de cette question ici sur SO, mais ils semblent tous expliquer la partie contrat d'une interface donc je ne suis pas sûr qu'ils s'appliquent.

10 votes

En essayant d'être un utilisateur sympa et ordonné, j'ai tendance à chercher d'abord ma réponse dans divers forums avant de poster quelque chose. Malheureusement, la plupart d'entre eux ont commencé plus tard et le reste ne m'a pas aidé. J'avais déjà du mal à répondre à la question de base "Pourquoi le faire ?", car il me semblait qu'il était inutile de trop compliquer les choses. Merci à tous pour les réponses très rapides. Je dois d'abord les digérer toutes, mais je pense que j'ai maintenant une assez bonne idée de leur intérêt. Il semble que j'ai toujours envisagé la question sous un angle différent. Merci beaucoup pour votre aide.

0 votes

Je veux juste dire que les interfaces sont difficiles à comprendre. Vous devez faire un grand effort pour les maîtriser. Si vous avez le temps, vous devriez lire ce livre : Injection de dépendances dans .NET . Il vous fera également découvrir de nombreuses idées connexes. Notez également que Python et C# sont assez différents. C# est typée statiquement.

495voto

KallDrexx Points 9020

Personne n'a vraiment expliqué en termes clairs l'utilité des interfaces, alors je vais tenter le coup (en reprenant un peu une idée de 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 est de préparer la commande dans le système. Chaque pizza doit être préparé mais chaque pizza est préparé différemment . Par exemple, lorsqu'une pizza à croûte farcie est commandée, le système doit probablement vérifier que certains ingrédients sont disponibles dans le restaurant et mettre de côté ceux qui ne sont pas nécessaires pour les pizzas profondes.

En écrivant cela en code, techniquement, vous pourriez 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 exiger que des propriétés différentes soient définies dans le fichier Prepare() que la croûte farcie, et donc vous vous retrouvez avec beaucoup de propriétés optionnelles, et la classe ne s'adapte pas bien (que faire si vous ajoutez de nouveaux types de pizza).

La bonne façon de résoudre ce problème est d'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 des commandes n'a pas besoin de savoir exactement quels types de pizzas ont été commandés pour traiter les ingrédients. Il a juste à le faire :

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 n'a pas à se soucier du type de pizza auquel nous avons affaire, elle sait simplement qu'elle est appelée pour des pizzas et donc chaque appel à Prepare préparera automatiquement chaque pizza correctement en fonction de son type, même si la collection comporte plusieurs types de pizzas.

38 votes

Bonne réponse, mais peut-être la modifier pour préciser pourquoi une interface dans ce cas est préférable à l'utilisation d'une classe abstraite (pour un exemple aussi simple, une classe abstraite peut en effet être meilleure ?)

45 votes

Je ne vois pas comment cela répond à la question. Cet exemple aurait pu tout aussi bien être fait avec une classe de base et une méthode abstraite, ce qui est exactement ce que la question initiale soulignait.

5 votes

Cette utilisation des interfaces n'est pas différente de l'utilisation d'une classe de base abstraite. Quel est l'avantage ?

200voto

Joey Points 148544

Le point est que l'interface représente un contrat . Un ensemble de méthodes publiques que toute classe d'implémentation doit posséder. Techniquement, l'interface ne régit que la syntaxe, c'est-à-dire les méthodes qui existent, les arguments qu'elles reçoivent et ce qu'elles renvoient. Habituellement, elles encapsulent également la sémantique, bien que cela soit uniquement documenté.

Vous pouvez alors avoir différentes implémentations d'une interface et les échanger à volonté. Dans votre exemple, puisque chaque instance de pizza est une interface IPizza vous pouvez utiliser IPizza partout où vous manipulez une instance d'un type de pizza inconnu. Toute instance dont le type hérite de IPizza est garanti comme pouvant être commandé, puisqu'il possède une fonction Order() méthode.

Python n'est pas typé statiquement, les types sont donc conservés et consultés au moment de l'exécution. Vous pouvez donc essayer d'appeler un Order() sur n'importe quel objet. Le runtime est satisfait tant que l'objet possède une telle méthode et se contente de hausser les épaules et de dire "Meh. Ce n'est pas le cas en C#. Le compilateur est responsable de faire les appels corrects et s'il a juste des méthodes aléatoires, il ne peut pas les utiliser. object le compilateur ne sait pas encore si l'instance pendant l'exécution aura cette méthode. Du point de vue du compilateur, c'est invalide puisqu'il ne peut pas le vérifier. (Vous pouvez faire ce genre de choses avec la réflexion ou la fonction dynamic mot-clé, mais c'est aller un peu loin pour le moment, je suppose).

Notez également qu'une interface au sens habituel du terme ne doit pas nécessairement être une interface C# interface Il peut également s'agir d'une classe abstraite ou même d'une classe normale (ce qui peut s'avérer utile si toutes les sous-classes doivent partager un code commun - dans la plupart des cas, cependant, interface suffit).

1 votes

+1, bien que je ne dirais pas que l'interface (au sens d'un contrat) peut être une classe abstraite ou normale.

3 votes

J'ajouterai que vous ne pouvez pas espérer comprendre les interfaces en quelques minutes. Je pense qu'il n'est pas raisonnable de comprendre les interfaces si vous n'avez pas des années d'expérience en programmation orientée objet. Vous pouvez ajouter des liens vers des livres. Je les suggère : Injection de dépendances dans .NET ce qui est vraiment la profondeur du trou du lapin, et pas seulement une introduction en douceur.

2 votes

Ah, voilà le problème : je n'ai aucune idée de ce qu'est le DI. Mais je pense que le problème principal du demandeur était de savoir pourquoi ils sont nécessaires alors qu'en Python tout fonctionne sans. C'est ce que le plus grand paragraphe de ma réponse a essayé de fournir. Je ne pense pas qu'il soit nécessaire de creuser dans chaque modèle et pratique qui utilise des interfaces ici.

147voto

Paddy Points 16834

Pour moi, lorsque j'ai commencé, l'intérêt de ces éléments n'est devenu clair que lorsque vous avez cessé de les considérer comme des éléments destinés à rendre votre code plus facile/plus rapide à écrire - ce n'est pas leur but. Ils ont un certain nombre d'utilisations :

(L'analogie avec la pizza va être perdue, car il n'est pas très facile de visualiser l'utilisation de ce système).

Disons que vous créez un jeu simple à l'écran et qu'il comporte des créatures avec lesquelles vous interagissez.

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

Vous pourriez écrire cela 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
    }
}

Partie avant :

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

    aTroll.Walk(1);
}

Deux semaines plus tard, les responsables du marketing décident que vous avez également besoin d'orques, car ils en ont entendu parler sur Twitter, et vous devez donc faire quelque chose comme ça :

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

Partie 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 désordonné. Vous pourriez utiliser une interface ici pour que votre front-end soit écrit une fois et (voici le point important) testé, et que vous puissiez ensuite ajouter d'autres éléments back-end si nécessaire :

interface ICreature
{
    void Walk(int distance)
}

public class Troll : ICreature
public class Orc : ICreature 

//etc

La partie avant est alors :

void SpawnCreature(creatureType)
{
    ICreature creature;

    switch(creatureType)
    {
         case Orc:

           creature = new Orc();

          case Troll:

            creature = new Troll();
    }

    creature.Walk();
}

Le frontal ne s'intéresse maintenant qu'à l'interface ICreature - il ne se soucie 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 l'on examine cette question de ce point de vue est que vous auriez également pu facilement utiliser une classe de créature abstraite, et de ce point de vue, cela a le même effet.

Et vous pourriez extraire la création vers 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();
}

Le front-end n'a même pas besoin d'avoir une référence à la bibliothèque où Troll et Orc sont implémentés (à condition que la fabrique soit 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 autrement homogène. par exemple

interface ICanTurnToStone
{
   void TurnToStone();
}

public class Troll: ICreature, ICanTurnToStone

La partie avant pourrait alors être :

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

    creature.Walk();

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

C : Utilisation de l'injection de dépendances

La plupart des cadres d'injection de dépendances fonctionnent lorsqu'il existe un couplage très lâche entre le code frontal et l'implémentation dorsale. Si nous prenons notre exemple de fabrique ci-dessus et que notre fabrique implémente une interface :

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

Notre frontal pourrait alors avoir cette injection (par exemple un contrôleur API MVC) par le biais du constructeur (typiquement) :

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 cadre 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'un ICreatureFactory est nécessaire dans un constructeur - cela rend notre code agréable et simple.

Cela signifie également que lorsque nous écrivons un test unitaire pour notre contrôleur, nous pouvons fournir une ICreatureFactory fantaisie (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 en dépendent) 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 "historiques", ne sont pas bien structurés, et A a une référence à B.

Vous trouvez alors une fonctionnalité dans B qui doit appeler une méthode déjà dans 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 en A implémente ensuite. 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.

7 votes

N'est-ce pas ennuyeux quand vous trouvez une réponse qui essayait de dire ce que vous disiez, mais qui le fait beaucoup mieux ? stackoverflow.com/a/93998/117215

23 votes

La bonne nouvelle est que maintenant c'est une page morte et que la vôtre ne l'est pas :). Un bon exemple !

1 votes

Votre discussion sur le C m'a un peu perdu. Mais j'aime vos discussions A et B parce qu'elles expliquent toutes deux essentiellement comment les interfaces peuvent être utilisées pour fournir des types de fonctionnalités communes à plusieurs classes. Le domaine des interfaces qui est encore flou pour moi est la façon dont les interfaces sont utilisées pour un couplage plus lâche. Peut-être est-ce là le sujet de votre discussion C ? Si oui, je suppose que j'ai besoin d'un exemple plus détaillé :)

36voto

Shamim Hafiz Points 8419

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
    }
}

13voto

BrokenGlass Points 91618

En l'absence de dactylographie du canard comme vous pouvez l'utiliser en Python, C# s'appuie sur les interfaces pour fournir des abstractions. Si les dépendances d'une classe étaient toutes des types concrets, vous ne pouviez pas passer dans un autre type - en utilisant les interfaces, vous pouvez passer dans tout type qui implémente l'interface.

5 votes

+1 C'est vrai, si vous avez la dactylographie du canard, vous n'avez pas besoin d'interfaces. Mais elles renforcent la sécurité des types.

Prograide.com

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

Powered by:

X