Question Que signifie «programmer sur une interface»?


J'ai vu cela mentionné à quelques reprises et je ne suis pas clair sur ce que cela signifie. Quand et pourquoi feriez-vous cela?

Je sais ce que les interfaces font, mais le fait que je ne suis pas clair sur ce fait me fait penser que je manque d'utiliser correctement.

Est-ce juste si vous deviez faire:

IInterface classRef = new ObjectWhatever()

Vous pouvez utiliser n'importe quelle classe qui implémente IInterface? Quand auriez-vous besoin de faire cela? La seule chose que je peux penser est si vous avez une méthode et vous ne savez pas quel objet sera passé attendez-vous pour la mise en œuvre IInterface. Je ne peux pas penser combien de fois vous auriez besoin de faire cela ... (Aussi, comment pourriez-vous écrire une méthode qui prend dans un objet qui implémente une interface? Est-ce possible?)

Désolé si j'ai complètement raté le point.


709
2017-12-21 00:48


origine


Réponses:


Il y a de merveilleuses réponses à ces questions qui entrent dans toutes sortes de détails sur les interfaces et le couplage lâche du code, l'inversion du contrôle, etc. Il y a des discussions assez capiteuses, alors j'aimerais profiter de l'occasion pour faire une petite analyse afin de comprendre pourquoi une interface est utile.

Quand j'ai commencé à être exposé aux interfaces, j'étais également confus quant à leur pertinence. Je n'ai pas compris pourquoi tu en avais besoin. Si nous utilisons un langage comme Java ou C #, nous avons déjà l'héritage et j'ai vu les interfaces comme plus faible forme d'héritage et de pensée, "pourquoi s'embêter?" Dans un sens j'avais raison, vous pouvez considérer les interfaces comme une sorte de faible héritage, mais au-delà, j'ai fini par comprendre leur utilisation en tant que construction du langage en les considérant comme un moyen de classer des traits ou des comportements communs. potentiellement beaucoup de classes d'objets non apparentés.

Par exemple - disons que vous avez un jeu SIM et que vous avez les classes suivantes:

 class HouseFly inherits Insect {
   void FlyAroundYourHead(){}
   void LandOnThings(){}
 }

 class Telemarketer inherits Person {
   void CallDuringDinner(){}
   void ContinueTalkingWhenYouSayNo(){}
 }

Clairement, ces deux objets n'ont rien de commun en termes d'héritage direct. Mais, vous pourriez dire qu'ils sont tous les deux agaçants.

Disons que notre jeu doit avoir une sorte de hasard chose cela agace le joueur quand il dîne. Cela pourrait être un HouseFly ou un Telemarketer ou les deux - mais comment permettez-vous pour les deux avec une seule fonction? Et comment demandez-vous à chaque type différent d'objet de "faire leur chose agaçante" de la même manière?

La clé à réaliser est qu'à la fois un Telemarketer et HouseFly partagent un comportement commun vaguement interprété même s'ils ne se ressemblent pas en termes de modélisation. Alors, faisons une interface que les deux peuvent implémenter:

 interface IPest {
    void BeAnnoying();
 }

 class HouseFly inherits Insect implements IPest {
   void FlyAroundYourHead(){}
   void LandOnThings(){}

   void BeAnnoying() {
     FlyAroundYourHead();
     LandOnThings();
   }
 }

 class Telemarketer inherits Person implements IPest {
   void CallDuringDinner(){}
   void ContinueTalkingWhenYouSayNo(){}

   void BeAnnoying() {
      CallDuringDinner();
      ContinueTalkingWhenYouSayNo();
   }
 }

Nous avons maintenant deux classes qui peuvent chacune être agaçantes à leur manière. Et ils n'ont pas besoin de dériver de la même classe de base et partagent des caractéristiques inhérentes communes - ils doivent simplement satisfaire le contrat de IPest Ce contrat est simple. Vous devez juste BeAnnoying. À cet égard, nous pouvons modéliser ce qui suit:

 class DiningRoom {

   DiningRoom(Person[] diningPeople, IPest[] pests) { ... }

   void ServeDinner() {
     when diningPeople are eating,

       foreach pest in pests
         pest.BeAnnoying();
   }
 }

Ici, nous avons une salle à manger qui accepte un certain nombre de convives et un certain nombre de parasites - notez l'utilisation de l'interface. Cela signifie que dans notre petit monde, un membre de la peststableau pourrait effectivement être un Telemarketer objet ou un HouseFly objet.

le ServeDinner méthode est appelée quand le dîner est servi et nos gens dans la salle à manger sont censés manger. Dans notre petit jeu, c'est à ce moment-là que nos ravageurs font leur travail - chaque ravageur est chargé d'être ennuyeux au moyen du IPest interface. De cette façon, nous pouvons facilement avoir les deux Telemarketers et HouseFlys être ennuyeux dans chacun de leurs propres moyens - nous nous soucions seulement que nous avons quelque chose dans le DiningRoom objet qui est un parasite, nous ne nous soucions pas vraiment de ce que c'est et ils ne pourraient rien avoir en commun avec les autres.

Cet exemple de pseudo-code très artificiel (qui a traîné beaucoup plus longtemps que prévu) est simplement destiné à illustrer le genre de chose qui m'a finalement éclairé sur le moment où nous pourrions utiliser une interface. Je m'excuse d'avance pour la bêtise de l'exemple, mais j'espère que cela aidera votre compréhension. Et, bien sûr, les autres réponses postées que vous avez reçues ici couvrent vraiment la gamme de l'utilisation des interfaces aujourd'hui dans les modèles de conception et les méthodologies de développement.


1402
2017-12-21 03:43



L'exemple spécifique que je donnais aux étudiants est qu'ils devraient écrire

List myList = new ArrayList(); // programming to the List interface

au lieu de

ArrayList myList = new ArrayList(); // this is bad

Ceux-ci semblent exactement les mêmes dans un programme court, mais si vous continuez à utiliser myList 100 fois dans votre programme, vous pouvez commencer à voir une différence. La première déclaration garantit que vous n'appelez que des méthodes sur myList qui sont définis par le List interface (donc pas ArrayList méthodes spécifiques). Si vous avez programmé l'interface de cette façon, plus tard vous pouvez décider que vous avez vraiment besoin

List myList = new TreeList();

et vous n'avez qu'à changer votre code à cet endroit. Vous savez déjà que le reste de votre code ne fait rien qui sera brisé en changeant le la mise en oeuvre parce que vous avez programmé le interface.

Les avantages sont encore plus évidents (je pense) quand vous parlez de paramètres de méthode et de valeurs de retour. Prenez ceci par exemple:

public ArrayList doSomething(HashMap map);

Cette déclaration de méthode vous relie à deux implémentations concrètes (ArrayList et HashMap). Dès que cette méthode est appelée à partir d'un autre code, toute modification de ces types signifie probablement que vous devrez également modifier le code appelant. Il vaudrait mieux programmer sur les interfaces.

public List doSomething(Map map);

Maintenant, peu importe quel genre de List vous revenez, ou quel genre de Map est passé en paramètre. Les changements que vous faites dans le doSomething La méthode ne vous forcera pas à changer le code appelant.


225
2017-12-21 01:35



Programmation à une interface dit, "j'ai besoin de cette fonctionnalité et je me fous d'où il vient."

Considérons (en Java), le List interface par rapport à la ArrayList et LinkedList classes concrètes. Si tout ce qui me concerne est que j'ai une structure de données contenant plusieurs éléments de données que je devrais accéder via l'itération, je choisirais un List (et c'est 99% du temps). Si je sais que j'ai besoin de l'insertion / suppression de temps constant de chaque extrémité de la liste, je pourrais choisir le LinkedList mise en œuvre concrète (ou plus probablement, utiliser le Queueinterface). Si je sais que j'ai besoin d'un accès aléatoire par index, je choisirais le ArrayList classe concrète.


61
2017-12-21 00:59



L'utilisation d'interfaces est un facteur clé pour rendre votre code facilement testable en plus de supprimer les couplages inutiles entre vos classes. En créant une interface qui définit les opérations sur votre classe, vous permettez aux classes qui veulent utiliser cette fonctionnalité de l'utiliser sans dépendre directement de votre classe d'implémentation. Si plus tard vous décidez de changer et d'utiliser une implémentation différente, vous n'avez qu'à modifier la partie du code où l'implémentation est instanciée. Le reste du code n'a pas besoin de changer car cela dépend de l'interface, pas de la classe d'implémentation.

Ceci est très utile pour créer des tests unitaires. Dans la classe testée, vous dépendez de l'interface et injectez une instance de l'interface dans la classe (ou une fabrique qui lui permet de construire des instances de l'interface si nécessaire) via le constructeur ou un settor de propriété. La classe utilise l'interface fournie (ou créée) dans ses méthodes. Quand vous allez écrire vos tests, vous pouvez simuler ou simuler l'interface et fournir une interface qui répond avec les données configurées dans votre test unitaire. Vous pouvez le faire car votre classe sous test ne traite que de l'interface, pas de votre implémentation concrète. Toute classe implémentant l'interface, y compris votre classe simulée ou fausse, fera l'affaire.

MODIFIER: Ci-dessous se trouve un lien vers un article où Erich Gamma discute de sa citation, "Programmer une interface, pas une implémentation".

http://www.artima.com/lejava/articles/designprinciples.html


35
2017-12-21 01:20



Vous devriez regarder dans Inversion of Control:

Dans un tel scénario, vous n'écririez pas ceci:

IInterface classRef = new ObjectWhatever();

Vous écririez quelque chose comme ceci:

IInterface classRef = container.Resolve<IInterface>();

Cela irait dans une configuration basée sur des règles dans le container objet, et construire l'objet réel pour vous, ce qui pourrait être ObjectWhatever. L'important est que vous puissiez remplacer cette règle par quelque chose qui utilise un autre type d'objet, et votre code fonctionnera toujours.

Si nous laissons IoC hors de la table, vous pouvez écrire du code qui sait qu'il peut parler à un objet cela fait quelque chose de spécifique, mais pas quel type d'objet ou comment il le fait.

Cela serait utile lors du passage des paramètres.

En ce qui concerne votre question entre parenthèse "De même, comment pourriez-vous écrire une méthode qui accepte un objet qui implémente une interface? Est-ce possible?", En C # vous utiliseriez simplement le type d'interface pour le type de paramètre, comme ceci:

public void DoSomethingToAnObject(IInterface whatever) { ... }

Cela se branche directement dans le "parler à un objet qui fait quelque chose de spécifique." La méthode définie ci-dessus sait à quoi s'attendre de l'objet, qu'il implémente tout dans IInterface, mais peu importe quel type d'objet il est, seulement qu'il adhère au contrat, qui est ce qu'est une interface.

Par exemple, vous êtes probablement familier avec les calculatrices et en avez probablement utilisé quelques-unes à l'époque, mais la plupart du temps elles sont toutes différentes. Vous, d'un autre côté, sait comment une calculatrice standard devrait fonctionner, donc vous pouvez les utiliser tous, même si vous ne pouvez pas utiliser les fonctionnalités spécifiques que chaque calculatrice a que l'autre n'a pas.

C'est la beauté des interfaces. Vous pouvez écrire un morceau de code, qui sait qu'il va recevoir des objets auxquels il peut s'attendre. Il ne se soucie pas de savoir quel type d'objet il est, mais seulement de supporter le comportement nécessaire.

Laissez-moi vous donner un exemple concret.

Nous avons un système de traduction sur mesure pour les formulaires Windows. Ce système boucle les contrôles sur un formulaire et traduit le texte dans chacun d'eux. Le système sait comment gérer les contrôles de base, comme la propriété-type-de-contrôle-qui-a-une-Text-text, et des choses de base similaires, mais pour tout ce qui est basique, cela ne répond pas.

Maintenant, puisque les contrôles héritent de classes prédéfinies sur lesquelles nous n'avons aucun contrôle, nous pourrions faire l'une des trois choses suivantes:

  1. Construire un support pour notre système de traduction pour détecter spécifiquement le type de contrôle avec lequel il travaille et traduire les bits corrects (cauchemar de maintenance)
  2. Construire le support en classes de base (impossible, puisque tous les contrôles héritent de différentes classes prédéfinies)
  3. Ajouter un support d'interface

Nous avons donc fait nr. 3. Tous nos contrôles implémentent ILocalizable, qui est une interface qui nous donne une méthode, la possibilité de traduire "lui-même" dans un conteneur de texte / règles de traduction. En tant que tel, le formulaire n'a pas besoin de savoir quel type de contrôle il a trouvé, seulement qu'il implémente l'interface spécifique, et sait qu'il existe une méthode où il peut appeler pour localiser le contrôle.


34
2017-12-21 00:58



La programmation sur une interface n'a absolument rien à voir avec les interfaces abstraites comme on le voit en Java ou .NET. Ce n'est même pas un concept de POO.

Qu'est-ce que cela signifie vraiment, c'est ne pas aller déconner avec les internes d'un objet ou d'une structure de données. Utilisez l'interface du programme abstrait, ou API, pour interagir avec vos données. En Java ou en C #, cela signifie utiliser des propriétés et des méthodes publiques au lieu de l'accès aux champs bruts. Pour C cela signifie utiliser des fonctions au lieu de pointeurs bruts.

EDIT: Et avec les bases de données cela signifie utiliser des vues et des procédures stockées au lieu de l'accès direct à la table.


28
2017-07-19 19:34



Code à l'interface Pas l'implémentation n'a rien à voir avec Java, ni sa construction d'interface. 

Ce concept a été mis en évidence dans les livres de Patterns / Gang of Four, mais il était très probablement bien avant cela. Le concept existait certainement bien avant que Java ait jamais existé. 

La construction de l'interface Java a été créée pour aider à cette idée (entre autres), et les gens sont devenus trop centrés sur la construction comme le centre de la signification plutôt que l'intention originale. Cependant, c'est la raison pour laquelle nous avons des méthodes et des attributs publics et privés en Java, C ++, C #, etc.

Cela signifie simplement interagir avec un objet ou une interface publique du système. Ne vous inquiétez pas et ne vous attendez même pas à ce qu'il fait en interne. Ne vous inquiétez pas de la façon dont il est mis en œuvre. Dans le code orienté objet, c'est pourquoi nous avons des méthodes / attributs publics vs. privés. Nous sommes destinés à utiliser les méthodes publiques car les méthodes privées ne sont là que pour une utilisation interne, au sein de la classe. Ils constituent l'implémentation de la classe et peuvent être modifiés selon les besoins sans changer l'interface publique. Supposons qu'en ce qui concerne la fonctionnalité, une méthode sur une classe effectuera la même opération avec le même résultat attendu chaque fois que vous l'appelez avec les mêmes paramètres. Il permet à l'auteur de changer le fonctionnement de la classe, sa mise en œuvre, sans casser la façon dont les gens interagissent avec elle.

Et vous pouvez programmer sur l'interface, pas sur l'implémentation sans jamais utiliser une construction Interface. Vous pouvez programmer sur l'interface pas l'implémentation en C ++, qui n'a pas de construction d'interface. Vous pouvez intégrer deux systèmes d'entreprise massifs de manière beaucoup plus robuste tant qu'ils interagissent via des interfaces publiques (contrats) plutôt que d'appeler des méthodes sur des objets internes aux systèmes. On s'attend à ce que les interfaces réagissent toujours de la même manière attendue compte tenu des mêmes paramètres d'entrée; si mis en œuvre à l'interface et non la mise en œuvre. Le concept fonctionne dans de nombreux endroits.

Secouez la pensée que les interfaces Java ont quelque chose à voir avec le concept de 'Programme à l'interface, pas la mise en œuvre'. Ils peuvent aider à appliquer le concept, mais ils sont ne pas le concept.


21
2018-04-14 19:36



Il semble que vous compreniez le fonctionnement des interfaces, mais que vous ne sachiez pas quand les utiliser et quels sont leurs avantages. Voici quelques exemples de quand une interface aurait du sens:

// if I want to add search capabilities to my application and support multiple search
// engines such as google, yahoo, live, etc.

interface ISearchProvider
{
    string Search(string keywords);
}

alors je pourrais créer GoogleSearchProvider, YahooSearchProvider, LiveSearchProvider etc.

// if I want to support multiple downloads using different protocols
// HTTP, HTTPS, FTP, FTPS, etc.
interface IUrlDownload
{
    void Download(string url)
}

// how about an image loader for different kinds of images JPG, GIF, PNG, etc.
interface IImageLoader
{
    Bitmap LoadImage(string filename)
}

Créez ensuite JpegImageLoader, GifImageLoader, PngImageLoader, etc.

La plupart des compléments et des sytèmes de plugin fonctionnent hors des interfaces.

Une autre utilisation populaire est pour le modèle Repository. Dites que je veux charger une liste de codes postaux de différentes sources

interface IZipCodeRepository
{
    IList<ZipCode> GetZipCodes(string state);
}

Ensuite, je pourrais créer un XMLZipCodeRepository, SQLZipCodeRepository, CSVZipCodeRepository, etc. Pour mes applications Web, je crée souvent des référentiels XML très tôt afin que je puisse obtenir quelque chose avant que la base de données Sql soit prête. Une fois la base de données prête, j'écris un SQLRepository pour remplacer la version XML. Le reste de mon code reste inchangé car il fonctionne uniquement à partir d'interfaces.

Les méthodes peuvent accepter des interfaces telles que:

PrintZipCodes(IZipCodeRepository zipCodeRepository, string state)
{
    foreach (ZipCode zipCode in zipCodeRepository.GetZipCodes(state))
    {
        Console.WriteLine(zipCode.ToString());
    }
}

12
2017-12-21 04:45



Il rend votre code beaucoup plus extensible et plus facile à maintenir lorsque vous avez des ensembles de classes similaires. Je suis un programmeur junior, donc je ne suis pas un expert, mais je viens de terminer un projet qui nécessitait quelque chose de similaire.

Je travaille sur un logiciel côté client qui parle à un serveur exécutant un appareil médical. Nous développons une nouvelle version de cet appareil qui contient de nouveaux composants que le client doit parfois configurer. Il y a deux types de nouveaux composants, et ils sont différents, mais ils sont également très similaires. Fondamentalement, j'ai dû créer deux formulaires de configuration, deux classes de listes, deux de tout.

J'ai décidé qu'il serait préférable de créer une classe de base abstraite pour chaque type de contrôle qui contiendrait presque toute la logique réelle, puis des types dérivés pour prendre en charge les différences entre les deux composants. Cependant, les classes de base n'auraient pas été capables d'effectuer des opérations sur ces composants si je devais m'inquiéter des types tout le temps (bien, ils auraient pu, mais il y aurait eu une instruction "if" ou un changement dans chaque méthode) .

J'ai défini une interface simple pour ces composants et toutes les classes de base parlent à cette interface. Maintenant, quand je change quelque chose, cela fonctionne à peu près partout et je n'ai pas de duplication de code.


10
2017-12-21 01:07



Si vous programmez en Java, JDBC est un bon exemple. JDBC définit un ensemble d'interfaces mais ne dit rien sur l'implémentation. Vos applications peuvent être écrites contre cet ensemble d'interfaces. En théorie, vous choisissez un pilote JDBC et votre application fonctionnerait simplement. Si vous découvrez qu'il existe un pilote JDBC plus rapide ou «meilleur» ou moins cher ou pour une raison quelconque, vous pouvez en théorie reconfigurer votre fichier de propriétés et, sans avoir à modifier votre application, votre application fonctionnera toujours.


8
2017-12-21 01:20