Interfaces - À quoi ça sert?


269

La raison des interfaces m'échappe vraiment. D'après ce que je comprends, c'est une sorte de contournement pour l'héritage multiple inexistant qui n'existe pas en C # (ou du moins m'a-t-on dit).

Tout ce que je vois, c'est que vous prédéfinissez certains membres et certaines fonctions, qui doivent ensuite être redéfinis dans la classe. Rendant ainsi l'interface redondante. Cela ressemble à de la syntaxe… eh bien, de la camelote pour moi (je vous en prie, aucune infraction ne voulait dire.

Dans l'exemple donné ci-dessous tiré d'un thread d'interfaces C # différent en cas de 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ébordement 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
    }
}

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

7
essayant d'être un utilisateur agréable et bien rangé, j'ai tendance à chercher ma réponse d'abord dans divers forums avant de poster quelque chose. Malheureusement, la plupart d'entre eux ont commencé à un stade ultérieur et le reste n'a pas aidé. Je me débattais déjà avec le basique "Pourquoi le faire?" car il me semblait inutile de trop compliquer les choses. Btw. Merci à tous pour les réponses très rapides. Je dois d'abord les digérer, mais je pense que j'ai maintenant une assez bonne idée de leur intérêt. Il semble que je l'ai toujours regardé sous un angle différent. Merci beaucoup pour votre aide.
Nebelhom


1
Les interfaces aident également à établir l'héritage comme pour les structtypes.
ja72

3
Hmm, l'OP demandait "D'après ce que je comprends, l'interface est une sorte de solution pour le multi-héritage inexistant qui n'existe pas en C #. utiliser une classe de base au lieu d'une interface ". Et puis la plupart des réponses ont soit donné un exemple qui peut être implémenté par une classe de base (abstraite), soit donné un exemple pour montrer comment l'interface est nécessaire pour le scénario multi-héritage. Ces réponses sont toutes bonnes, mais ne réitèrent-elles pas simplement quelque chose que le PO sait déjà? Pas étonnant qu'OP ait fini par choisir une réponse sans exemples. LOL
RayLuo

Réponses:


176

Le fait 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 quelles méthodes sont là, quels arguments elles obtiennent et ce qu'elles renvoient. Habituellement, ils encapsulent également la sémantique, bien que cela uniquement par la documentation.

Vous pouvez alors avoir différentes implémentations d'une interface et les échanger à volonté. Dans votre exemple, puisque chaque instance de pizza est un, IPizzavous pouvez l'utiliser IPizzapartout où vous gérez une instance d'un type de pizza inconnu. Toute instance dont le type hérite IPizzaest garantie d'être ordonnable, car elle a une Order()méthode.

Python n'est pas typé statiquement, donc les types sont conservés et recherchés lors de l'exécution. Vous pouvez donc essayer d'appeler une Order()méthode sur n'importe quel objet. Le runtime est heureux tant que l'objet a une telle méthode et probablement juste hausse les épaules et dit »Meh.« Si ce n'est pas le cas. Ce n'est pas le cas en C #. Le compilateur est responsable de faire les appels corrects et s'il a juste un peu aléatoire, objectle compilateur ne sait pas encore si l'instance pendant l'exécution aura cette méthode. Du point de vue du compilateur, il n'est pas valide car il ne peut pas le vérifier. (Vous pouvez faire de telles choses avec la réflexion ou le dynamicmot - clé, mais cela va un peu loin en ce moment, je suppose.)

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


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

3
J'ajouterais que vous ne pouvez pas vous attendre à 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 suggérerais: l' injection de dépendance dans .NET qui est vraiment la profondeur du trou de lapin, pas seulement une introduction douce.
knut

2
Ah, il y a le problème de ne pas avoir la moindre idée de DI. Mais je suppose que le principal problème du demandeur était 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.
Joey

1
Eh bien, alors votre question devient: "Pourquoi utiliser des langages typés statiquement quand les langages typés dynamiquement sont plus faciles à programmer?". Bien que je ne sois pas l'expert pour répondre à cette question, je peux m'aventurer à dire que la performance est la question décisive. Lorsqu'un appel à un objet python est effectué, le processus doit décider pendant l'exécution si cet objet a une méthode appelée "Order", alors que si vous appelez un objet C #, il est déjà établi qu'il implémente cette méthode, et le appel peut être fait à une telle adresse.
Boluc Papuccuoglu

3
@BolucPapuccuoglu: Au-delà de cela, avec un objet de type statique si l'on connaît des fooimplémentations IWidget, un programmeur qui voit un appel à foo.woozle()peut consulter la documentation IWidgetet savoir ce que cette méthode est censée faire . Le programmeur n'a peut-être aucun moyen de savoir d'où proviendra le code de l'implémentation réelle, mais tout type respectant le IWidgetcontrat d'interface sera implémenté food'une manière cohérente avec ce contrat. Dans un langage dynamique, en revanche, il n'y aurait pas de point de référence clair pour ce qui foo.woozle()devrait signifier.
supercat

440

Personne n'a vraiment expliqué en termes clairs comment les interfaces sont utiles, donc je vais essayer (et voler un peu la réponse de Shamim).

Prenons l'idée d'un service de commande de pizza. 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ée mais chaque pizza est préparée 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 au restaurant et mettre de côté ceux qui ne sont pas nécessaires pour les pizzas à plats profonds.

Lors de l'écriture 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 à vaisselle profonde (en termes C #) peuvent nécessiter des propriétés différentes à définir dans la Prepare()méthode que la croûte farcie, et donc vous vous retrouvez avec beaucoup de propriétés facultatives, et la classe ne se met pas à l'échelle correctement (que faire si vous ajoutez de nouvelles 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 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
    }
}

Maintenant, votre code de traitement des commandes n'a pas besoin de savoir exactement quels types de pizzas ont été commandés pour gérer 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 n'a pas à se soucier du type de pizza que nous traitons, il sait juste qu'il est appelé pour des pizzas et donc chaque appel à Preparepréparera automatiquement chaque pizza correctement en fonction de son type, même si la collection comprend plusieurs types de pizzas.


11
+1, j'ai aimé l'exemple de l'utilisation d'une liste pour montrer l'utilisation d'une interface.
danielunderwood

21
Bonne réponse, mais peut-être la modifier pour clarifier pourquoi une interface dans ce cas est meilleure que simplement utiliser une classe abstraite (pour un exemple aussi simple, une classe abstraite peut en effet être meilleure?)
Jez

3
C'est la meilleure réponse. Il y a une petite faute de frappe ou peut-être vous suffit de copier et coller le code. Dans l'interface C #, vous ne déclarez pas de modificateurs d'accès tels que public. Donc, à l'intérieur de l'interface, cela devrait être justevoid Prepare();
Dush

26
Je ne vois pas comment cela répond à la question. Cet exemple aurait pu tout aussi facilement être fait avec une classe de base et une méthode abstraite, ce qui est exactement ce que la question d'origine a souligné.
Jamie Kitson

4
Les interfaces ne viennent pas vraiment à leur droite tant que vous n'en avez pas beaucoup. Vous ne pouvez hériter que d'une seule classe, abstraite ou non, mais vous pouvez implémenter autant d'interfaces différentes dans une seule classe que vous le souhaitez. Tout à coup, vous n'avez pas besoin d'une porte dans un cadre de porte; tout ce qui est IOpenable fera, que ce soit une porte, une fenêtre, une boîte aux lettres, vous l'appelez.
mtnielsen

123

Pour moi, au début, l'intérêt de ceux-ci n'est devenu clair que lorsque vous cessez de les regarder comme des choses pour rendre votre code plus facile / plus rapide à écrire - ce n'est pas leur but. Ils ont un certain nombre d'utilisations:

(Cela va perdre l'analogie avec la 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 qu'il aura des créatures avec lesquelles vous interagirez.

R: Ils peuvent faciliter la maintenance de votre code à l'avenir en introduisant un couplage lâche entre votre implémentation frontale et votre implémentation back end.

Vous pourriez é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'Orcs, comme ils en lisent 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 désordonné. Vous pouvez utiliser une interface ici pour que votre front-end soit écrit une fois et (voici le bit important) testé, et vous pouvez ensuite brancher d'autres éléments back-end si nécessaire:

interface ICreature
{
    void Walk(int distance)
}

public class Troll : ICreature
public class Orc : ICreature 

//etc

L'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 se soucie désormais que de l'interface ICreature - il ne se soucie pas de l'implémentation interne d'un troll ou d'un orc, mais uniquement du fait qu'ils implémentent ICreature.

Un point important à noter lorsque l'on regarde cela 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 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();
}

Désormais, le front-end 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 soit dans une bibliothèque séparée) - il n'a besoin de rien savoir à leur sujet.

B: Supposons que vous ayez des fonctionnalités 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

Le frontal 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épendances

La plupart des frameworks d'injection de dépendances sont plus faciles à utiliser lorsqu'il y a un couplage très lâche entre le code frontal et la mise en œuvre du serveur principal. Si nous prenons notre exemple d'usine ci-dessus et que notre usine implémente une interface:

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

Notre frontal pourrait alors avoir injecté ceci (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 framework DI (par exemple Ninject ou Autofac), nous pouvons les configurer de telle sorte qu'au moment de l'exécution, une instance de CreatureFactory sera 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 simulée (par exemple, si l'implémentation concrète nécessite un accès DB, nous ne voulons pas que nos tests unitaires en dépendent) et tester facilement le code dans notre contrôleur .

D: Il existe 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 trouverez ensuite des fonctionnalités dans B qui doivent 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 de A implémente ensuite. Votre méthode en B peut passer 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.


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

18
La bonne nouvelle est maintenant une page morte et la vôtre ne l'est pas :). Bon exemple!
Sealer_05

1
Votre discussion en C m'a un peu perdu. Mais j'aime vos discussions A et B, car elles expliquent essentiellement comment les interfaces peuvent être utilisées pour fournir des types de fonctionnalités communs à 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. C'est peut-être de cela que parlait votre discussion sur C? Si oui, je suppose que j'ai besoin d'un exemple plus détaillé :)
user2075599

1
Il me semble dans votre exemple que ICreature serait mieux adapté pour être une classe de base abstraite (Créature?) Pour les Trolls et les Orques. Ensuite, toute logique commune entre les créatures pourrait y être implémentée. Ce qui signifie que vous n'avez pas du tout besoin de l'interface ICreature ...
Skarsnik

1
@Skarsnik - c'est tout à fait le sujet de cette note: "Un point important à noter quand on regarde cela 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."
Paddy

33

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

27

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 itérer sans savoir quelle classe se trouve en haut.

Un exemple plus adéquat serait l'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 les utiliser tous MenuItemet 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();
    }
}

2
J'ai dû faire défiler tout en bas ici pour trouver une réponse qui répond réellement à la question "pourquoi ne pas simplement utiliser une classe abstraite au lieu d'une interface?". Il semble que c'est vraiment pour gérer uniquement le manque d'héritage multiple de c #.
SyntaxRules

2
Oui, c'est la meilleure explication pour moi. La seule qui explique clairement pourquoi l'interface peut être plus utile qu'une classe abstraite. (c.-à-d.: une pizza est un MenuItem ET est également de la nourriture tandis qu'avec une classe abstraite, elle ne peut être que l'une ou l'autre mais pas les deux)
Maxter

25

Explication simple avec analogie

Le problème à résoudre: quel est le but du polymorphisme?

Analogie: Je suis donc un contremaître sur un chantier de construction.

Des commerçants marchent tout le temps sur le chantier. Je ne sais pas qui va franchir ces portes. Mais je leur dis essentiellement quoi faire.

  1. Si c'est un menuisier, je dis: construisez des échafaudages en bois.
  2. Si c'est un plombier, je dis: "Installez les tuyaux"
  3. Si c'est un électricien, je dis: «Retirez les câbles et remplacez-les par des câbles à fibres optiques».

Le problème avec l'approche ci-dessus est que je dois: (i) savoir qui marche dans cette porte, et selon qui c'est, je dois leur dire quoi faire. Cela signifie que je dois tout savoir sur un métier particulier. Il y a des coûts / avantages associés à cette approche:

Les implications de savoir quoi faire:

  • Cela signifie que si le code du charpentier passe de: BuildScaffolding()à BuildScaffold()(c'est- à -dire un léger changement de nom), je Forepersondevrai également changer la classe appelante (c'est-à-dire la classe) également - vous devrez apporter deux modifications au code au lieu de (essentiellement ) juste un. Avec le polymorphisme, vous n'avez (fondamentalement) qu'à effectuer un seul changement pour obtenir le même résultat.

  • Deuxièmement, vous n'aurez pas à demander constamment: qui êtes-vous? ok fais ça ... qui es-tu? ok fais ça ..... polymorphisme - il SÈCHE ce code, et est très efficace dans certaines situations:

  • avec le polymorphisme, vous pouvez facilement ajouter des classes supplémentaires de gens de métier sans modifier le code existant. (c'est-à-dire le deuxième des principes de conception SOLIDE: principe d'ouverture-fermeture).

La solution

Imaginez un scénario où, peu importe qui passe la porte, je peux dire: "Work ()" et ils font leur travail de respect dans lequel ils se spécialisent: le plombier s'occupe des tuyaux et l'électricien s'occupe des fils.

L'avantage de cette approche est que: (i) je n'ai pas besoin de savoir exactement qui entre par cette porte - tout ce que je dois savoir, c'est qu'ils seront un type de métier et qu'ils peuvent faire du travail, et d'autre part , (ii) je n'ai pas besoin de savoir quoi que ce soit sur ce commerce particulier. Le tradie s'en chargera.

Donc au lieu de ça:

If(electrician) then  electrician.FixCablesAndElectricity() 

if(plumber) then plumber.IncreaseWaterPressureAndFixLeaks() 

Je peux faire quelque chose comme ça:

ITradesman tradie = Tradesman.Factory(); // in reality i know it's a plumber, but in the real world you won't know who's on the other side of the tradie assignment.

tradie.Work(); // and then tradie will do the work of a plumber, or electrician etc. depending on what type of tradesman he is. The foreman doesn't need to know anything, apart from telling the anonymous tradie to get to Work()!!

Quel est l'avantage?

L'avantage est que si les exigences spécifiques du charpentier, etc. changent, le contremaître n'aura pas besoin de changer son code - il n'a pas besoin de savoir ou de se soucier. Tout ce qui compte, c'est que le menuisier sache ce que veut dire Work (). Deuxièmement, si un nouveau type de travailleur de la construction vient sur le chantier, le contremaître n'a pas besoin de savoir quoi que ce soit sur le métier - tout le contremaître se soucie si le travailleur de la construction (.eg soudeur, vitrier, carreleur, etc.) peut faire travailler ().


Problème et solution illustrés (avec et sans interfaces):

Pas d'interface (exemple 1):

Exemple 1: sans interface

Pas d'interface (exemple 2):

Exemple 2: sans interface

Avec une interface:

Exemple 3: les avantages de l'utilisation d'une interface

Résumé

Une interface vous permet de demander à la personne d'effectuer le travail qui lui est assigné, sans que vous ayez la connaissance exacte de qui elle est ni des détails de ce qu'elle peut faire. Cela vous permet d'ajouter facilement de nouveaux types (de commerce) sans changer votre code existant (eh bien techniquement, vous le changez un tout petit peu), et c'est le véritable avantage d'une approche OOP par rapport à une méthodologie de programmation plus fonctionnelle.

Si vous ne comprenez rien de ce qui précède ou si ce n'est pas clair, demandez dans un commentaire et j'essaierai d'améliorer la réponse.


4
Plus un pour le gars qui arrive au travail dans un sweat à capuche.
Yusha

11

En l'absence de typage canard 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 des types concrets, vous ne pourriez pas passer dans un autre type - en utilisant des interfaces, vous pouvez passer dans n'importe quel type qui implémente l'interface.


3
+1 C'est vrai, si vous avez tapé du canard, vous n'avez pas besoin d'interfaces. Mais ils imposent une sécurité de type plus forte.
Groo

11

L'exemple Pizza est mauvais car vous devez 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 vos classes héritent de différents endroits, ou lorsque vous n'avez pas de code commun que vous pourriez utiliser. Par exemple, il s'agit de choses qui peuvent être éliminées IDisposable, vous savez qu'elles seront éliminées, vous ne savez tout simplement pas ce qui se passera lorsqu'elles seront éliminées.

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


10

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

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

Supposons que vous soyez en train de créer des contrôles personnalisés. Vous souhaitez créer de nouveaux boutons, zones de texte, listes de visualisation, grilles, etc., et vous souhaitez qu'ils aient tous certaines fonctionnalités propres à votre ensemble de contrôles.

Par exemple, vous souhaiterez peut-être une façon commune de gérer les thèmes ou une façon commune de gérer la localisation.

Dans ce cas, vous ne pouvez pas "simplement créer une classe de base" car si vous le faites, vous devez réimplémenter tout ce 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 pouvez-vous maintenant identifier quels contrôles sont "à vous", comment pouvez-vous créer du code qui dit "pour tous les contrôles du formulaire qui sont les miens, définissez le thème sur X".

Entrez les interfaces.

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

Vous devez créer "YourButton", descendre de Button et ajouter la prise en charge de toutes les interfaces dont vous avez besoin.

Cela vous permettrait d'écrire du code comme celui-ci:

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

Cela ne serait pas possible sans interfaces, à la place, vous devriez écrire du code comme ceci:

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

Oui, je sais, vous ne devriez pas utiliser "est" et ensuite lancer, laissez cela de côté.
Lasse V. Karlsen

4
soupire, je sais, mais c'est accessoire ici.
Lasse V. Karlsen

7

Dans ce cas, vous pourriez (et le feriez probablement) simplement définir une classe de base Pizza et en hériter. Cependant, il y a deux raisons pour lesquelles les interfaces vous permettent de faire des choses qui ne peuvent pas être réalisées autrement:

  1. Une classe peut implémenter plusieurs interfaces. Il définit simplement les fonctionnalités que la classe doit avoir. 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 étendue 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 l'implémentation de cela ailleurs.

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


6

Considérez que vous ne pouvez pas utiliser l'héritage multiple en C #, puis réexaminez votre question.



4

Si je travaille sur une API pour dessiner des formes, je peux vouloir utiliser DirectX ou des appels graphiques, ou OpenGL. Donc, je vais créer une interface, qui résumera mon implémentation de ce que vous appelez.

Donc , vous appelez une méthode d'usine: MyInterface i = MyGraphics.getInstance(). Ensuite, vous avez un contrat, vous savez donc à quelles fonctions vous pouvez vous attendre MyInterface. Ainsi, vous pouvez appeler i.drawRectangleou i.drawCubeet savoir que si vous échangez une bibliothèque pour une autre, les fonctions sont prises en charge.

Cela devient plus important si vous utilisez l'injection de dépendances, car vous pouvez alors, dans un fichier XML, échanger les implémentations.

Ainsi, vous pouvez avoir une bibliothèque de chiffrement qui peut être exportée à usage général et une autre qui est à vendre uniquement aux entreprises américaines, et la différence est que vous modifiez un fichier de configuration, et le reste du programme n'est pas modifié.

Cela est beaucoup utilisé avec les collections en .NET, car vous devez simplement utiliser, par exemple, des Listvariables, 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 la mise en œuvre 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, donc, je n'ai pas à aller à une base de données, mais à une implémentation simulée qui renvoie simplement des données statiques, donc je peux tester ma méthode sans se soucier si la base de données est en panne pour maintenance ou non.


4

Une interface est vraiment un contrat que les classes d'implémentation doivent suivre, c'est en fait la base de presque tous les modèles de conception que je connais.

Dans votre exemple, l'interface est créée car alors tout ce qui EST UNE Pizza, ce qui signifie implémente l'interface Pizza, est garanti d'avoir implémenté

public void Order();

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

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 importe, c'est que vos objets répondent à order ().


4

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

L'une des raisons absolument cruciales pour utiliser des interfaces dans un projet orienté objet est qu'elles vous permettent de privilégier la composition plutôt que l'héritage. En implémentant des interfaces, vous pouvez dissocier vos implémentations des différents algorithmes que vous leur appliquez.

Ce superbe tutoriel "Motif Décorateur" de Derek Banas (qui - assez drôle - utilise également la pizza comme exemple) en est une illustration intéressante:

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


2
Je suis vraiment choqué que ce ne soit pas la meilleure réponse. Les interfaces, dans toute leur utilité, sont les plus utiles en composition.
Maciej Sitko,

3

Les interfaces servent à appliquer la 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 sa propre façon de brûler. donc vous faites simplement;

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

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

2
La question qui me hante dans des exemples comme celui-ci est: pourquoi cela aide-t-il? Puis-je maintenant passer l'argument de type IBurnable à une méthode et toutes les classes avec l'interface IBurnable peuvent être gérées? Identique à l'exemple de pizza que j'ai trouvé. C'est bien que vous puissiez faire cela, mais je ne vois pas l'avantage en tant que tel. Pourriez-vous étendre l'exemple (parce que je suis vraiment épais en ce moment) ou donner un exemple qui ne fonctionnerait pas avec lui (encore une fois, parce que je me sens vraiment épais en ce moment). Merci beaucoup.
Nebelhom

1
Se mettre d'accord. Interface = "Peut faire". Class / Abstract Calss = "Is A"
Peter.Wang

3

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

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


3

Je suis surpris que peu de messages contiennent la raison la plus importante pour une interface: Design Patterns . C'est la vue d'ensemble de l'utilisation des contrats, et bien qu'il s'agisse d'une décoration syntaxique pour le 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.

Développons l'analogie avec la pizza pour dire un repas complet à 3 plats. Nous aurons toujours l' Prepare()interface de base pour toutes nos catégories d'aliments, mais nous aurions également des déclarations abstraites pour les sélections de cours (entrée, plat, dessert), et des propriétés différentes pour les types d'aliments (salé / sucré, végétarien / non végétarien, sans gluten, etc.).

Sur la base de ces spécifications, nous avons pu implémenter le modèle Abstract Factory pour conceptualiser l'ensemble du processus, mais utiliser des interfaces pour nous assurer que seules les fondations étaient en béton. Tout le reste pourrait devenir flexible ou encourager le polymorphisme, tout en conservant l'encapsulation entre les différentes classes Coursequi implémentent l' ICourseinterface.

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


1
Cette réponse mérite plus de points! Les interfaces brillent lorsqu'elles sont utilisées dans des modèles conçus, par exemple le modèle d'état. Voir plus.google.com/+ZoranHorvat-Programming pour plus d'informations
Alex Nolasco

3

Voici une interface pour les objets qui ont une forme rectangulaire:

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

Tout ce qu'il faut, c'est que vous mettiez en œuvre des moyens d'accéder à la largeur et à la hauteur de l'objet.

Définissons maintenant une méthode qui fonctionnera sur n'importe quel objet qui est IRectangular:

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

Cela retournera la zone de tout objet rectangulaire.

Implémentons une classe SwimmingPoolrectangulaire:

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 Housequi est également 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; }
}

Cela dit, vous pouvez appeler la Areaméthode 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 façon, vos classes peuvent "hériter" du comportement (méthodes statiques) de n'importe quel nombre d'interfaces.


3

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


3

Quelle ?

Les interfaces sont fondamentalement un contrat que toutes les classes implémentant l'interface doivent suivre. Ils ressemblent à une classe mais n'ont aucune implémentation.

Dans les C#noms d'interface par convention est défini par le préfixe d'un «I» donc si vous voulez avoir une interface appelée formes, vous devez le déclarer commeIShapes

Maintenant pourquoi ?

Improves code re-usability

Disons que vous voulez dessiner Circle, Triangle. vous pouvez les regrouper et les appeler Shapeset avoir des méthodes pour dessiner Circleet Triangle mais avoir une implémentation concrète serait une mauvaise idée car demain vous pourriez décider d'en avoir 2 de plus Shapes Rectangle& Square. Maintenant, lorsque vous les ajoutez, il y a de grandes chances que vous cassiez d'autres parties de votre code.

Avec Interface, vous isolez les différentes implémentations du contrat


Scénario en direct, jour 1

On vous a demandé de créer une application pour dessiner des cercles et des triangles

interface IShapes
{
   void DrawShape();
   
 }

class Circle : IShapes
{
    
    public void DrawShape()
    {
        Console.WriteLine("Implementation to Draw a Circle");
    }
}

Class Triangle: IShapes
{
     public void DrawShape()
    {
        Console.WriteLine("Implementation to draw a Triangle");
    }
}
static void Main()
{
     List <IShapes> shapes = new List<IShapes>();
        shapes.Add(new Circle());
        shapes.Add(new Triangle());

        foreach(var shape in shapes)
        {
            shape.DrawShape();
        }
}

Scénario en direct, jour 2

Si on vous a demandé d'ajouter Squareet d'y ajouter Rectangle, tout ce que vous avez à faire est de créer l'implémentation pour cela dans class Square: IShapeset en Mainajouter à la listeshapes.Add(new Square());


Pourquoi ajoutez-vous une réponse à une question de 6 ans avec des dizaines d'autres réponses votées des centaines de fois? Il n'y a rien ici qui n'ait déjà été dit.
Jonathon Reinhart

@JonathonReinhart, oui, je le pensais, mais alors je pensais à cet exemple et la façon dont son explication aidera à mieux comprendre un corps que les autres.
Clint

2

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 SOLIDES de la conception orientée objet. En résumé:

S - Principe de responsabilité unique O - Principe ouvert / fermé L - Principe de substitution Liskov I - Principe de ségrégation d'interface D - Principe d'inversion de dépendance

Le respect des 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 des dépendances est une grande victoire. Les interfaces et le principe d'inversion des dépendances aident vraiment à dissocier le code des dépendances sur des classes concrètes, de sorte que le code peut être écrit et raisonné en termes de comportements plutôt que d'implémentations. Cela permet de diviser le code en composants qui peuvent être composés au moment de l'exécution plutôt qu'au moment de la compilation et signifie également que ces composants peuvent être facilement branchés et déconnectés sans avoir à modifier le reste du code.

Les interfaces aident en particulier avec le principe d'inversion de dépendance, où le code peut être intégré dans une collection 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 critique si vous commencez à écrire des tests unitaires et à utiliser le développement piloté par les tests. Essayez! Vous comprendrez rapidement comment les interfaces aident à séparer le code en morceaux gérables qui peuvent être testés individuellement de manière isolée.


1

Le principal objectif des interfaces est de conclure un contrat entre vous et toute autre classe qui implémente cette interface, ce qui découplera votre code et permettra son extensibilité.


1

Thérèse nous demande de très bons exemples.

Un autre, dans le cas d'une instruction switch, vous n'avez plus besoin de maintenir et de changer chaque fois que vous voulez que rio exécute 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 soin de sa propre logique.

Cela aide à réduire le couplage et la complexité cyclomatique. Vous devez toujours mettre en œuvre la logique, mais il y aura moins de choses à garder à l'esprit dans le cadre plus large.

Pour chaque pizza, vous pouvez ensuite 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 ont besoin de savoir.


1

La façon la plus simple de penser aux interfaces est de reconnaître ce que signifie l'héritage. Si la classe CC hérite de la classe C, cela signifie à la fois que:

  1. La classe CC peut utiliser tous les membres publics ou protégés de la classe C comme s'ils étaient les siens, et n'a donc besoin que d'implémenter des éléments qui n'existent pas dans la classe parente.
  2. Une référence à un CC peut être passée ou affectée à une routine ou une variable qui attend une référence à un C.

Ces deux fonctions de l'héritage sont en quelque sorte indépendantes; bien que l'héritage s'applique simultanément aux deux, il est également possible d'appliquer la seconde sans la première. Ceci est utile car autoriser un objet à hériter des membres de deux ou plusieurs classes non liées est beaucoup plus compliqué que d'autoriser un type de chose à ê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 cela (sous-utilisé dans le cadre .net, à mon humble avis) est qu'ils permettent d'indiquer de manière déclarative les choses qu'un objet peut faire. Certains objets, par exemple, voudront un objet de source de données à partir duquel ils pourront récupérer des choses par index (comme cela est possible avec une liste), mais ils n'auront pas besoin d'y stocker quoi que ce soit. D'autres routines auront besoin d'un objet de dépôt de données où elles peuvent stocker des choses non par index (comme avec Collection.Add), mais elles n'auront pas besoin de lire quoi que ce soit. Certains types de données autorisent l'accès par index, mais ne permettent pas l'écriture; d'autres autoriseront l'écriture, mais ne permettront pas l'accès par index. Certains, bien sûr, permettront les deux.

Si ReadableByIndex et Appendable étaient des classes de base non apparentées, il serait impossible de définir un type qui pourrait être transmis à la fois aux choses qui attendent un ReadableByIndex et aux choses qui attendent un Appendable. On pourrait essayer d'atténuer cela en faisant dériver ReadableByIndex ou Appendable de l'autre; la classe dérivée devrait mettre à disposition des membres publics aux deux fins, mais avertir que certains membres publics pourraient ne pas fonctionner réellement. Certaines des classes et interfaces de Microsoft le font, mais c'est plutôt compliqué. Une approche plus propre consiste à avoir des interfaces pour les différents objectifs, puis à faire en sorte que les objets implémentent des interfaces pour les choses qu'ils peuvent réellement faire. Si l'un avait une interface IReadableByIndex et une autre interface IAppendable,


1

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

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


1

Pour moi, un avantage / avantage d'une interface est qu'elle est plus flexible qu'une classe abstraite. Comme vous ne pouvez hériter que d'une classe abstraite mais que vous pouvez implémenter plusieurs interfaces, les modifications d'un système qui hérite d'une classe abstraite à de nombreux endroits deviennent problématiques. S'il est hérité à 100 endroits, un changement nécessite des changements à tous les 100. Mais, avec l'interface, vous pouvez placer le nouveau changement dans une nouvelle interface et utiliser simplement cette interface là où elle est nécessaire (Interface Séq. De SOLID). De plus, l'utilisation de la mémoire semble être moindre avec l'interface car un objet dans l'exemple d'interface n'est utilisé qu'une seule fois en mémoire malgré le nombre d'endroits qui implémentent l'interface.


1

Les interfaces sont utilisées pour favoriser la cohérence, d'une manière qui est vaguement couplée, ce qui la rend différente de la classe abstraite qui est étroitement couplée. C'est pourquoi elle est également communément définie comme un contrat. défini par l'interface et il n'y a pas d'éléments concrets en son sein.

Je vais juste donner un exemple soutenu par le graphique ci-dessous.

Imaginez dans une usine, il existe 3 types de machines: une machine rectangle, une machine triangle et une machine polygone.Les temps sont compétitifs et vous souhaitez rationaliser la formation des opérateurs.Vous voulez simplement les former à une méthodologie de démarrage et d'arrêt des machines afin que vous ont un bouton de démarrage vert et un bouton d'arrêt rouge. Maintenant, sur 3 machines différentes, vous avez un moyen cohérent de démarrer et d'arrêter 3 types de machines différents.Imaginez maintenant que ces machines sont des classes et que les classes doivent avoir des méthodes de démarrage et d'arrêt, comment vous va conduire à la cohérence entre ces classes qui peuvent être très différentes? L'interface est la réponse.

entrez la description de l'image ici

Un exemple simple pour vous aider à visualiser, on pourrait se demander pourquoi ne pas utiliser la classe abstraite? Avec une interface, les objets n'ont pas besoin d'être directement liés ou hérités et vous pouvez toujours générer de la cohérence entre différentes classes.

public interface IMachine
{
    bool Start();
    bool Stop();
}

public class Car : IMachine
{
    public bool Start()
    {
        Console.WriteLine("Car started");
        return true;
    }

    public bool Stop()
    {
        Console.WriteLine("Car stopped");
        return false;
    }
}

public class Tank : IMachine
{
    public bool Start()
    {
        Console.WriteLine("Tank started");
        return true;
    }

    public bool Stop()
    {
        Console.WriteLine("Tank stopped");
        return false;
    }
}

class Program
{
    static void Main(string[] args)
    {
        var car = new Car();
        car.Start();
        car.Stop();

        var tank = new Tank();
        tank.Start();
        tank.Stop();

    }
}

1
class Program {
    static void Main(string[] args) {
        IMachine machine = new Machine();
        machine.Run();
        Console.ReadKey();
    }

}

class Machine : IMachine {
    private void Run() {
        Console.WriteLine("Running...");
    }
    void IMachine.Run() => Run();
}

interface IMachine
{
    void Run();
}

Permettez-moi de décrire cela sous un angle différent. Créons une histoire selon l'exemple que j'ai montré ci-dessus;

Program, Machine et IMachine sont les acteurs de notre histoire. Le programme veut s'exécuter mais il n'a pas cette capacité et Machine sait comment l'exécuter. Machine et IMachine sont les meilleurs amis mais le programme n'est pas en accord avec Machine. Ainsi, Program et IMachine concluent un accord et décident qu'IMachine dira à Program comment fonctionner en regardant Machine (comme un réflecteur).

Et le programme apprend à fonctionner à l'aide d'IMachine.

L'interface permet la communication et le développement de projets faiblement couplés.

PS: J'ai la méthode de la classe concrète comme privée. Mon objectif ici est de parvenir à un couplage lâche en empêchant d'accéder aux propriétés et méthodes de classe concrètes, et de ne laisser que le moyen de les atteindre via des interfaces. (J'ai donc défini les méthodes des interfaces de manière explicite).


1

Je sais que je suis très en retard .. (presque neuf ans), mais si quelqu'un veut une petite explication, vous pouvez opter pour ceci:

En termes simples, vous utilisez Interface lorsque vous savez ce qu'un objet peut faire ou quelle fonction nous allons implémenter sur un objet. Exemple Insérer, mettre à jour et supprimer.

interface ICRUD{
      void InsertData(); // will insert data
      void UpdateData(); // will update data
      void DeleteData(); // will delete data
}

Remarque importante: les interfaces sont TOUJOURS publiques.

J'espère que cela t'aides.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.