J'étudie et je code en C # depuis un certain temps maintenant. Mais encore, je ne peux pas comprendre l'utilité des interfaces. Ils apportent trop peu à la table. En plus de fournir les signatures de fonction, ils ne font rien. Si je me souviens des noms et de la signature des fonctions qui doivent être implémentées, il n'y en a pas besoin. Ils sont là juste pour s'assurer que lesdites fonctions (dans l'interface) sont implémentées dans la classe héritière.
C # est un excellent langage, mais il vous donne parfois le sentiment que Microsoft crée d'abord le problème (ne permettant pas l'héritage multiple) et fournit ensuite la solution, qui est plutôt fastidieuse.
C'est ma compréhension qui est basée sur une expérience de codage limitée. Quel est votre point de vue sur les interfaces? À quelle fréquence les utilisez-vous et qu'est-ce qui vous pousse à le faire?
Ils sont là juste pour s'assurer que lesdites fonctions (dans l'interface) sont implémentées dans la classe héritière.
Correct. C'est un avantage suffisamment impressionnant pour justifier la fonctionnalité. Comme d'autres l'ont dit, une interface est une obligation contractuelle pour implémenter certaines méthodes, propriétés et événements. L'avantage convaincant d'un langage typé statiquement est que le compilateur peut vérifier qu'un contrat sur lequel votre code s'appuie est réellement respecté.
Cela dit, les interfaces sont un moyen assez faible de représenter les obligations contractuelles. Si vous souhaitez un moyen plus fort et plus flexible de représenter les obligations contractuelles, examinez la fonctionnalité Contrats de code fournie avec la dernière version de Visual Studio.
C # est un excellent langage, mais il vous donne parfois le sentiment que Microsoft crée d'abord le problème (n'autorisant pas l'héritage multiple) puis fournit la solution, qui est plutôt fastidieuse.
Eh bien, je suis content que vous l'aimiez.
Toutes les conceptions de logiciels complexes sont le résultat de la pondération des fonctionnalités en conflit les unes par rapport aux autres, et de la recherche du "point idéal" qui offre de grands avantages pour de petits coûts. Nous avons appris par une expérience douloureuse que les langages qui permettent l'héritage multiple à des fins de partage d'implémentation ont des avantages et des coûts relativement faibles. L'autorisation de l'héritage multiple uniquement sur les interfaces, qui ne partagent pas les détails d'implémentation, offre de nombreux avantages de l'héritage multiple sans la plupart des coûts.
Donc, dans cet exemple, le PowerSocket ne sait rien d'autre sur les autres objets. Les objets dépendent tous de la puissance fournie par le PowerSocket, ils implémentent donc IPowerPlug et, ce faisant, ils peuvent s'y connecter.
Les interfaces sont utiles car elles fournissent des contrats que les objets peuvent utiliser pour travailler ensemble sans avoir besoin de se connaître.
En plus de fournir les signatures de fonction, ils ne font rien. Si je me souviens des noms et de la signature des fonctions qui doivent être implémentées, il n'y en a pas besoin
Le point des interfaces n'est pas de vous aider à vous souvenir de la méthode à implémenter, il s'agit ici de définir un contrat . Dans exemple foreach P.Brian.Mackey (ce qui se révèle être faux , mais peu nous importe), IEnumerable définit un contrat entre foreach et toute chose énumérable. Il dit: "Qui que vous soyez, tant que vous respectez le contrat (implémentez IEnumerable), je vous promets de répéter tous vos éléments". Et c'est super (pour un langage non dynamique).
Grâce aux interfaces, vous pouvez obtenir un couplage très faible entre deux classes.
Les interfaces sont le meilleur moyen de maintenir des constructions bien découplées.
Lors de la rédaction des tests, vous constaterez que les classes concrètes ne fonctionneront pas dans votre environnement de test.
Exemple: Vous souhaitez tester une classe qui dépend d'une classe Data Access Service . Si cette classe parle à un service Web ou à une base de données - votre test unitaire ne s'exécutera pas dans votre environnement de test (en plus, il s'est transformé en test d'intégration).
Solution? Utilisez un Interface pour votre Service d'accès aux données et Mock cette interface afin que vous puissiez tester votre classe comme une unité.
D'un autre côté, WPF et Silverlight ne jouent pas du tout avec les interfaces en matière de liaison. C'est une ride assez méchante.
Les interfaces sont l'épine dorsale du polymorphisme (statique)! L'interface est ce qui compte. L'héritage ne fonctionnerait pas sans interfaces, car les sous-classes héritent essentiellement de l'interface déjà implémentée du parent.
À quelle fréquence vous en faites usage et qu'est-ce qui vous pousse à le faire ??
Assez souvent. Tout ce qui doit être enfichable est une interface dans mes applications. Souvent, vous avez des classes non liées qui doivent fournir le même comportement. Vous ne pouvez pas résoudre de tels problèmes avec l'héritage.
Besoin d'algorithmes différents pour effectuer des opérations sur les mêmes données? Utilisez une interface ( voir modèle de stratégie )!
Voulez-vous utiliser différentes implémentations de liste? Code contre une interface et l'appelant n'a pas à se soucier de la mise en œuvre!
Il a été considéré comme une bonne pratique (pas seulement dans la POO) de coder contre des interfaces pendant des siècles, pour une seule raison: il est facile de changer une implémentation lorsque vous réalisez qu'elle ne correspond pas à vos besoins. C'est assez lourd si vous essayez de réaliser cela uniquement avec un héritage multiple ou si cela revient à créer des classes vides afin de fournir l'interface nécessaire.
Vous avez probablement utilisé foreach
et l'avez trouvé comme un outil d'itération assez utile. Saviez-vous qu'il nécessite une interface pour fonctionner, IEnumerable ?
C'est certainement un cas concret parlant de l'utilité d'une interface.
Les interfaces servent à coder des objets comme une prise au câblage domestique. Souhaitez-vous souder votre radio directement au câblage de votre maison? Et votre aspirateur? Bien sûr que non. La fiche et la prise dans laquelle elle s'insère forment "l'interface" entre le câblage de votre maison et l'appareil qui en a besoin. Le câblage de votre maison ne doit rien savoir de l'appareil, sauf qu'il utilise une fiche à trois broches mise à la terre et nécessite une alimentation électrique à 120 VCA <= 15 A. À l'inverse, l'appareil ne nécessite aucune connaissance mystérieuse du câblage de votre maison, à part le fait qu'il dispose d'une ou de plusieurs prises à trois broches idéalement situées qui fournissent 120 VCA <= 15 A.
Les interfaces remplissent une fonction très similaire dans le code. Un objet peut déclarer qu'une variable, un paramètre ou un type de retour particulier est d'un type d'interface. L'interface ne peut pas être instanciée directement avec un mot clé new
, mais mon objet peut recevoir, ou trouver, l'implémentation de cette interface avec laquelle il devra travailler. Une fois que l'objet a sa dépendance, il n'a pas besoin de savoir exactement ce qu'est cette dépendance, il doit juste savoir qu'il peut appeler les méthodes X, Y et Z sur la dépendance. Les implémentations de l'interface n'ont pas besoin de savoir comment elles seront utilisées, elles doivent juste savoir qu'elles devront fournir des méthodes X, Y et Z avec des signatures particulières.
Ainsi, en faisant abstraction de plusieurs objets derrière la même interface, vous fournissez un ensemble commun de fonctionnalités à tout consommateur d'objets de cette interface. Vous n'avez pas besoin de savoir que l'objet est, par exemple, une liste, un dictionnaire, une LinkedList, une OrderedList ou autre. Parce que vous savez que tous ces éléments sont des IEnumerables, vous pouvez utiliser les méthodes de IEnumerable pour parcourir chaque élément de ces collections un par un. Vous n'avez pas besoin de savoir qu'une classe de sortie est un ConsoleWriter, un FileWriter, un NetworkStreamWriter ou même un MulticastWriter qui prend d'autres types d'écrivains; tout ce que vous devez savoir, c'est qu'ils sont tous des IWriters (ou quoi que ce soit), et donc ils ont une méthode "Write" dans laquelle vous pouvez passer une chaîne, et cette chaîne sera sortie.
Bien qu'il soit clairement un régal pour le programmeur (au moins au début) d'avoir un héritage multiple, c'est une omission presque triviale, et vous ne devriez pas (dans la plupart des cas) compter sur l'héritage multiple. Les raisons en sont complexes, mais si vous vraiment voulez en savoir plus, considérez l'expérience des deux langages de programmation les plus connus (par index TIOBE ) qui prennent en charge il: C++ et Python (3e et 8e respectivement).
En Python, l'héritage multiple est pris en charge, mais il est presque universellement mal compris par les programmeurs et dire que vous savez comment cela fonctionne, signifie lire et comprendre ce document sur le sujet: Ordre de résolution des méthodes . Quelque chose d'autre, qui s'est produit en Python, est que les interfaces sont entrées dans le langage - Zope.Interfaces.
Pour C++, google "diamant hiérarchie C++" et voyez la laideur qui est sur le point de vous couvrir. Les pros C++ savent utiliser l'héritage multiple. Tout le monde joue généralement sans savoir quels seront les résultats. Une autre chose qui montre l'utilité des interfaces est le fait que, dans de nombreux cas, une classe peut avoir besoin de remplacer complètement le comportement de son parent. Dans de tels cas, l'implémentation parent est inutile et ne charge la classe enfant que de la mémoire pour les variables privées du parent, ce qui peut ne pas avoir d'importance à l'ère C #, mais est important lorsque vous effectuez une programmation intégrée. Si vous utilisez une interface, ce problème est inexistant.
En conclusion, les interfaces sont, à mon avis, une partie essentielle de la POO, car elles font respecter un contrat. L'héritage multiple est utile dans des cas limités, et généralement uniquement aux hommes qui savent l'utiliser. Donc, si vous êtes débutant, vous êtes celui qui est traité par le manque d'héritage multiple - cela vous donne une meilleure chance de ne pas faire d'erreur.
En outre, historiquement, l'idée d'une interface est enracinée bien avant les spécifications de conception C # de Microsoft. La plupart des gens considèrent C # comme une mise à niveau par rapport à Java (dans la plupart des sens), et devinent d'où C # a obtenu ses interfaces - Java. Le protocole est un mot plus ancien pour le même concept, et c'est une manière plus ancienne que .NET.
Mise à jour: Maintenant, je vois que j'ai peut-être répondu à une question différente - pourquoi les interfaces au lieu de l'héritage multiple, mais cela semblait être la réponse que vous cherchiez. Outre une OO langue devrait avoir au moins l'une des deux, et les autres réponses ont couvert votre question d'origine.
Il m'est difficile d'imaginer du code C # propre et orienté objet sans utiliser d'interfaces. Vous les utilisez chaque fois que vous souhaitez appliquer la disponibilité de certaines fonctionnalités sans forcer les classes à hériter d'une classe de base spécifique, ce qui permet à votre code d'avoir le niveau de couplage (bas) approprié.
Je ne suis pas d'accord pour dire que l'héritage multiple est meilleur que d'avoir des interfaces, avant même de faire valoir que l'héritage multiple s'accompagne de son propre ensemble de douleurs. Les interfaces sont un outil de base pour permettre le polymorphisme et la réutilisation de code, de quoi a-t-on besoin de plus?
Je peux dire que je me rapporte à cela. Quand j'ai commencé à me renseigner sur OO et C #, moi non plus, je n'ai pas eu d'interfaces. Ça va. Nous avons juste besoin de trouver quelque chose qui vous fera apprécier les commodités des interfaces.
Permettez-moi d'essayer deux approches. Et pardonnez-moi pour les généralisations.
Essayez 1
Supposons que vous soyez anglophone natif. Vous allez dans un autre pays où l'anglais n'est pas la langue maternelle. Vous avez besoin d'aide. Vous avez besoin de quelqu'un qui peut vous aider.
Demandez-vous: "Hé, êtes-vous né aux États-Unis?" C'est l'héritage.
Ou demandez-vous: "Hé, tu parles anglais"? Ceci est l'interface.
Si vous vous souciez de ce qu'il fait, vous pouvez compter sur des interfaces. Si vous vous souciez de ce qui est, vous comptez sur l'héritage.
C'est ok de compter sur l'héritage. Si vous avez besoin de quelqu'un qui parle anglais, aime le thé et aime le football, vous feriez mieux de demander un Britannique. :)
Essayez 2
Ok, essayons un autre exemple.
Vous utilisez différentes bases de données et vous devez implémenter des classes abstraites pour travailler avec elles. Vous passerez votre classe à une classe du fournisseur DB.
public abstract class SuperDatabaseHelper
{
void Connect (string User, string Password)
}
public abstract class HiperDatabaseHelper
{
void Connect (string Password, string User)
}
Héritage multiple, dites-vous? Essayez cela avec le cas ci-dessus. Tu ne peux pas. Le compilateur ne saura pas quelle méthode Connect vous essayez d'appeler.
interface ISuperDatabaseHelper
{
void Connect (string User, string Password)
}
interface IHiperDatabaseHelper
{
void Connect (string Password, string User)
}
Maintenant, il y a quelque chose avec lequel nous pouvons travailler - au moins en C # - où nous pouvons implémenter des interfaces explicitement.
public class MyDatabaseHelper : ISuperDatabaseHelper, IHiperDatabaseHelper
{
IHiperDataBaseHelper.Connect(string Password, string User)
{
//
}
ISuperDataBaseHelper.Connect(string User, string Password)
{
//
}
}
Conclusion
Les exemples ne sont pas les meilleurs, mais je pense que cela fait le point.
Vous n'obtiendrez des interfaces que lorsque vous en ressentez le besoin. Jusqu'à ce que vous pensiez qu'ils ne sont pas pour vous.
Personnellement, j'aime la classe abstraite et je l'utilise plus qu'une interface. La principale différence vient de l'intégration avec des interfaces .NET telles que IDisposable, IEnumerable et ainsi de suite ... et avec COM interop. En outre, l'interface est un peu moins d'effort à écrire qu'une classe abstraite, et une classe peut implémenter plusieurs interfaces alors qu'elle ne peut hériter que d'une seule classe.
Cela dit, je trouve que la plupart les choses pour lesquelles j'utiliserais une interface sont mieux servies par une classe abstraite. Les fonctions virtuelles pures - fonctions abstraites - vous permettent de forcer un implémenteur à définir une fonction similaire à la façon dont une interface force un implémenteur à définir tous ses membres.
Cependant, vous utilisez généralement une interface lorsque vous ne souhaitez pas imposer une certaine conception à la super classe, tandis que vous utilisez une classe abstraite pour avoir une conception réutilisable qui est déjà principalement implémentée.
J'ai largement utilisé des interfaces pour écrire des environnements de plugins en utilisant l'espace de noms System.ComponentModel. Ils sont très utiles.
Il y a 2 raisons principales:
Les interfaces en elles-mêmes ne sont pas très utiles. Mais lorsqu'il est implémenté par des classes concrètes, vous voyez qu'il vous donne la flexibilité d'avoir une ou plusieurs implémentations. Le bonus est que l'objet utilisant l'interface n'a pas besoin de savoir comment vont les détails de l'implémentation réelle - c'est ce qu'on appelle l'encapsulation.
L'utilisation d'interfaces aide un système à rester découplé et donc plus facile à refactoriser, changer et redéployer. C'est un concept très fondamental de l'orthodoxie orientée objet et je l'ai appris pour la première fois lorsque les gourous du C++ ont créé des "classes abstraites pures" qui sont tout à fait équivalentes aux interfaces.
En tant que jeune programmeur/développeur, tout en apprenant C #, vous ne verrez peut-être pas l'utilité de l'interface, car vous pourriez écrire vos codes à l'aide de vos classes et le code fonctionne bien, mais dans la réalité, la construction d'une application évolutive, robuste et maintenable implique d'utiliser certaines architectures et modèles, qui ne peuvent être rendus possibles qu'en utilisant une interface, par exemple, dans l'injection de dépendances.
Ils sont principalement utilisés pour la réutilisation du code. Si vous codez pour l'interface, vous pouvez utiliser une classe différente qui hérite de cette interface et ne pas tout casser.
De plus, ils sont très utiles dans les services Web où vous voulez faire savoir au client ce que fait une classe (afin qu'ils puissent la consommer) mais ne voulez pas leur donner le code réel.
Une mise en œuvre réelle:
Vous pouvez convertir un objet en tant que type d'interface:
IHelper h = (IHelper)o;
h.HelperMethod();
Vous pouvez créer une liste d'une interface
List<IHelper> HelperList = new List<IHelper>();
Avec ces objets, vous pouvez accéder à n'importe quelle méthode ou propriété d'interface. De cette manière, vous pouvez définir une interface pour votre partie d'un programme. Et construisez la logique qui l'entoure. Ensuite, quelqu'un d'autre peut implémenter votre interface dans leurs objets métier. Si le BO change, il peut changer la logique des composants d'interface et ne pas nécessiter de changement de logique pour votre pièce.
Ce qui suit est un pseudocode:
class MyClass{
private MyInterface = new MyInterfaceImplementationB();
// Code using Thingy
}
interface MyInterface{
myMethod();
}
class MyInterfaceImplementationA{ myMethod(){ // method implementation A } }
class MyInterfaceImplementationB{ myMethod(){ // method implementation B } }
class MyInterfaceImplementationC{ myMethod(){ // method implementation C } }
Les dernières classes peuvent être des implémentations complètement différentes.
À moins que l'héritage multiple soit possible, l'héritage impose l'implémentation de la classe parente ce qui rend les choses plus rigides. La programmation contre des interfaces, d'autre part, peut permettre à votre code ou à un framework d'être extrêmement flexible. Si vous rencontrez un cas où vous souhaitiez pouvoir échanger des classes dans une chaîne d'héritage, vous comprendrez pourquoi.
Par exemple, un cadre qui fournit un Reader initialement destiné à lire des données à partir du disque pourrait être réimplémenté pour faire quelque chose de la même nature mais d'une manière totalement différente. Comme interpréter le code Morse par exemple.
Les interfaces se prêtent à la modularité de type plugin en fournissant un mécanisme permettant aux classes de comprendre (et de s'abonner) à certains types de messages que votre système délivre. Je vais élaborer.
Dans votre application, vous décidez que chaque fois qu'un formulaire est chargé ou rechargé, vous souhaitez que toutes les choses qu'il héberge soient effacées. Vous définissez une interface IClear
qui implémente Clear
. En outre, vous décidez que chaque fois que l'utilisateur clique sur le bouton Enregistrer, le formulaire doit tenter de conserver son état. Ainsi, tout ce qui respecte ISave
reçoit un message pour conserver son état. Bien sûr, pratiquement toutes les interfaces gèrent plusieurs messages.
Ce qui distingue les interfaces, c'est qu'un comportement commun peut être obtenu sans héritage. La classe qui implémente une interface donnée comprend simplement comment se comporter lorsqu'elle émet une commande (un message de commande) ou comment répondre lorsqu'elle est interrogée (un message de requête). Essentiellement, les classes de votre application comprennent les messages fournis par votre application. Cela facilite la construction d'un système modulaire dans lequel les choses peuvent être branchées.
Dans la plupart des langues, il existe des mécanismes (comme LINQ ) pour interroger les choses qui respectent une interface. Cela vous aidera généralement à éliminer la logique conditionnelle, car vous n'aurez pas à dire à des choses différentes (qui ne sont pas nécessairement dérivées de la même chaîne d'héritage) comment se comporter de manière similaire (selon un message particulier). Au lieu de cela, vous rassemblez tout ce qui comprend un certain message (respecte une interface) et publiez le message.
Par exemple, vous pouvez remplacer ...
Me.PublishDate.Clear()
Me.Subject.Clear()
Me.Body.Clear()
...avec:
For Each ctl As IClear In Me.Controls.OfType(Of IClear)()
ctl.Clear()
Next
Ce qui ressemble beaucoup à:
Écoutez, écoutez! Est-ce que tout le monde qui comprend la suppression, s'il vous plaît
Clear
maintenant!
De cette façon, nous pouvons éviter par programmation de dire à chaque chose de se nettoyer. Et lorsque des éléments effaçables sont ajoutés à l'avenir, ils répondent simplement sans code supplémentaire.