Que signifie «programmer sur une interface»?


814

J'ai vu cela mentionné plusieurs fois et je ne sais pas ce que cela signifie. Quand et pourquoi feriez-vous cela?

Je sais ce que font les interfaces, mais le fait que je ne sois pas clair à ce sujet me fait penser que je manque de les utiliser correctement.

En est-il de même si vous deviez faire:

IInterface classRef = new ObjectWhatever()

Vous pourriez utiliser n'importe quelle classe qui implémente IInterface? Quand auriez-vous besoin de faire cela? La seule chose à laquelle je peux penser, c'est si vous avez une méthode et que vous n'êtes pas sûr de quel objet sera passé, sauf pour sa mise en œuvre IInterface. Je ne peux pas penser à quelle fréquence vous auriez besoin de le faire.

Aussi, comment pourriez-vous écrire une méthode qui prend un objet qui implémente une interface? Est-ce possible?


3
Si vous vous en souvenez et que votre programme doit être optimal, juste avant la compilation, vous souhaiterez peut-être échanger la déclaration d'interface pour la mise en œuvre réelle. L'utilisation d'une interface ajoute un niveau d'indirection qui donne un coup de performance. Distribuez votre code programmé sur les interfaces ...
Ande Turner

18
@Ande Turner: c'est un mauvais conseil. 1). "votre programme doit être optimal" n'est pas une bonne raison pour échanger des interfaces! Ensuite, vous dites "Distribuez votre code programmé sur les interfaces si ..." donc vous conseillez que, compte tenu de l'exigence (1), vous libérez ensuite du code sous-optimal?!?
Mitch Wheat

74
La plupart des réponses ici ne sont pas tout à fait exactes. Cela ne signifie pas ou n'implique même pas du tout "utiliser le mot-clé d'interface". Une interface est une spécification de la façon d'utiliser quelque chose - synonyme du contrat (recherchez-le). La mise en œuvre est distincte de cela, et c'est ainsi que ce contrat est exécuté. Programmer uniquement contre les garanties de la méthode / du type de sorte que, lorsque la méthode / le type est modifié d'une manière qui respecte toujours le contrat, il ne casse pas le code en l'utilisant.
jyoungdev

2
@ apollodude217 qui est en fait la meilleure réponse sur toute la page. Au moins pour la question dans le titre, car il y a au moins 3 questions assez différentes ici ...
Andrew Spencer

4
Le problème fondamental avec des questions comme celle-ci est qu'il suppose que «programmer vers une interface» signifie «tout envelopper dans une interface abstraite», ce qui est stupide si vous considérez que le terme est antérieur au concept d'interfaces abstraites de style Java.
Jonathan Allen

Réponses:


1635

Il y a ici 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, donc j'aimerais profiter de l'occasion pour décomposer un peu les choses pour comprendre pourquoi une interface est utile.

Lorsque j'ai commencé à être exposé aux interfaces, j'étais moi aussi confus quant à leur pertinence. Je ne comprenais pas pourquoi tu en avais besoin. Si nous utilisons un langage comme Java ou C #, nous avons déjà l'héritage et j'ai considéré les interfaces comme une forme d'héritage plus faible et je me suis dit "pourquoi s'embêter?" Dans un sens, j'avais raison, vous pouvez considérer les interfaces comme une sorte de forme d'héritage faible, mais au-delà de cela, j'ai finalement compris leur utilisation en tant que construction de langage en les considérant comme un moyen de classer les traits ou comportements communs qui étaient exposés par potentiellement de nombreuses classes d'objets non lié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(){}
}

Il est clair que ces deux objets n'ont rien de commun en termes d'héritage direct. Mais, on pourrait dire qu'ils sont tous les deux ennuyeux.

Disons que notre jeu doit avoir une sorte de chose aléatoire qui agace le joueur quand il mange. Cela peut être un HouseFlyou un Telemarketerou les deux - mais comment autorisez-vous les deux avec une seule fonction? Et comment demandez-vous à chaque type d'objet différent de "faire sa chose ennuyeuse" de la même manière?

La clé à réaliser est que a Telemarketeret HouseFlypartagent un comportement commun mal interprété, même s'ils ne sont rien en termes de modélisation. Faisons donc 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 gênantes à leur manière. Et ils n'ont pas besoin de dériver de la même classe de base et de partager des caractéristiques inhérentes communes - ils doivent simplement satisfaire le contrat de IPest- ce contrat est simple. Tu dois juste BeAnnoying. À cet égard, nous pouvons modéliser les éléments suivants:

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 du peststableau pourrait en fait être un Telemarketerobjet ou un HouseFlyobjet.

La ServeDinnerméthode est appelée lorsque le dîner est servi et que nos gens dans la salle à manger sont censés manger. Dans notre petit jeu, c'est à ce moment que nos nuisibles font leur travail - chaque nuisible est invité à être ennuyeux via l' IPestinterface. De cette façon, nous pouvons facilement avoir les deux Telemarketerset HouseFlysêtre ennuyeux de chacune de leurs manières - nous nous soucions seulement que nous ayons quelque chose dans l' DiningRoomobjet qui soit un parasite, nous ne nous soucions pas vraiment de ce qu'il est et ils ne pourraient rien avoir dans commun avec d'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 a finalement allumé la lumière pour moi en termes de moment où nous pourrions utiliser une interface. Je m'excuse à l'avance pour la bêtise de l'exemple, mais j'espère que cela vous aidera à comprendre. Et, bien sûr, les autres réponses publié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.


3
Une autre chose à considérer est que, dans certains cas, il pourrait être utile d'avoir une interface pour les choses qui «pourraient» être ennuyeuses et d'avoir une variété d'objets mis BeAnnoyingen œuvre en tant que no-op; cette interface peut exister à la place ou en plus de l'interface pour les choses gênantes (si les deux interfaces existent, l'interface "choses qui sont gênantes" devrait hériter de l'interface "choses qui pourraient être gênantes"). L'inconvénient de l'utilisation de telles interfaces est que les implémentations peuvent être gênées par l'implémentation d'un nombre "ennuyeux" de méthodes de stub. L'avantage est que ...
supercat

4
Les méthodes ne sont pas destinées à représenter des méthodes abstraites - leur mise en œuvre n'est pas pertinente pour la question qui portait sur les interfaces.
Peter Meyer

33
Encapsuler des comportements, tels que IPest est connu comme le modèle de stratégie juste au cas où quelqu'un serait intéressé à suivre avec plus de matériel sur ce sujet ...
nckbrz

9
Fait intéressant, vous ne IPest[]précisez pas que parce que les objets dans sont des références IPest, vous pouvez appeler BeAnnoying()parce qu'ils ont cette méthode, alors que vous ne pouvez pas appeler d'autres méthodes sans transtypage. Cependant, chaque BeAnnoying()méthode individuelle des objets sera appelée.
D. Ben Knoble

4
Très bonne explication ... J'ai juste besoin de le dire ici: je n'ai jamais entendu parler d'interfaces comme une sorte de mécanisme d'héritage lâche, mais à la place, je sais que l'héritage est utilisé comme un mécanisme médiocre pour définir des interfaces (par exemple, en Python normal, vous faites-le tout le temps).
Carlos H Romano

283

L'exemple spécifique que j'ai donné aux étudiants est qu'ils doivent écrire

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

au lieu de

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

Ceux-ci se ressemblent exactement dans un programme court, mais si vous continuez à utiliser myList100 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 myListdéfinies par l' Listinterface (donc pas de ArrayListméthodes spécifiques). Si vous avez programmé l'interface de cette façon, plus tard, vous pourrez 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 cassé en changeant l' implémentation parce que vous avez programmé l' interface .

Les avantages sont encore plus évidents (je pense) lorsque 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 lie à deux implémentations concrètes ( ArrayListet 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 serait préférable de programmer sur les interfaces.

public List doSomething(Map map);

Peu importe le type de Listretour que vous faites ou le type de Mapparamètre transmis. Les modifications que vous apportez à l'intérieur de la doSomethingméthode ne vous forceront pas à modifier le code appelant.


Les commentaires ne sont pas pour une discussion approfondie; cette conversation a été déplacée vers le chat .
Yvette

1
Explication très claire. Très utile
samuel luswata

J'ai une question sur la raison que vous avez mentionnée. "La première déclaration garantit que vous n'appelez sur myList que des méthodes définies par l'interface List (donc pas de méthodes spécifiques à ArrayList). Si vous avez programmé l'interface de cette façon, plus tard vous peut décider que vous avez vraiment besoin de List myList = new TreeList (); et vous n'avez qu'à changer votre code à cet endroit. " J'ai peut-être mal compris, je me demande pourquoi vous devez changer ArrayList en TreeList si vous voulez "vous assurer que vous n'appelez que des méthodes sur myList"?
user3014901

1
@ user3014901 Il existe un certain nombre de raisons pour lesquelles vous souhaiterez peut-être modifier le type de liste que vous utilisez. On pourrait avoir de meilleures performances de recherche, par exemple. Le fait est que si vous programmez sur l'interface List, il sera plus facile de changer votre code vers une implémentation différente plus tard.
Bill the Lizard

73

La programmation sur une interface signifie: "J'ai besoin de cette fonctionnalité et je m'en fiche d'où elle vient."

Considérez (en Java), l' Listinterface par rapport aux classes ArrayListet LinkedListconcrètes. Si tout ce qui m'importe, c'est que j'ai une structure de données contenant plusieurs éléments de données auxquels je dois accéder via l'itération, je choisirais un List(et c'est 99% du temps). Si je sais que j'ai besoin d'insertion / suppression à temps constant de l'une ou l'autre extrémité de la liste, je pourrais choisir l' LinkedListimplémentation concrète (ou plus probablement, utiliser l' interface de file d'attente ). Si je sais que j'ai besoin d'un accès aléatoire par index, je choisirais la ArrayListclasse concrète.


1
totalement d'accord, c'est-à-dire l'indépendance entre ce qui est fait et la façon dont cela est fait. En partitionnant un système le long de composants indépendants, vous vous retrouvez avec un système simple et réutilisable (voir Simple Made Easy par le gars qui a créé Clojure)
beluchin

38

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 autorisez les classes qui souhaitent utiliser cette fonctionnalité à l'utiliser sans dépendre directement de votre classe d'implémentation. Si par la suite vous décidez de modifier 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 il 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 sous test, vous devez la faire dépendre de l'interface et injecter une instance de l'interface dans la classe (ou une fabrique qui lui permet de construire des instances de l'interface selon les besoins) via le constructeur ou un configurateur de propriété. La classe utilise l'interface fournie (ou créée) dans ses méthodes. Lorsque 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 en cours de test ne traite que de l'interface, pas de votre implémentation concrète. Toute classe implémentant l'interface, y compris votre classe factice ou fausse, fera l'affaire.

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

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


4
S'il vous plaît, relisez cette interview: Gamma parlait bien sûr du concept d'interface OO, pas de la classe spéciale JAVA ou C # (ISomething). Le problème est que la plupart des gens parlaient du mot-clé, donc nous avons maintenant beaucoup d'interfaces inédites (ISomething).
Sylvain Rodrigue

Très bonne interview. S'il vous plaît, faites attention aux futurs lecteurs, il y a quatre pages dans l'interview. Je fermerais presque le navigateur avant de le voir.
Ad Infinitum

38

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

Ce que cela signifie, c'est de ne pas jouer avec les éléments internes d'un objet ou d'une structure de données. Utilisez l'interface de programme abstraite ou API pour interagir avec vos données. En Java ou C #, cela signifie utiliser des propriétés et des méthodes publiques au lieu d'un accès brut aux champs. 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 d'un accès direct à la table.


5
Meilleure réponse. Gamma donne une explication similaire ici: artima.com/lejava/articles/designprinciples.html (voir page 2). Il fait référence au concept OO mais vous avez raison: c'est plus grand que ça.
Sylvain Rodrigue

36

Vous devriez vous pencher sur l'inversion du contrôle:

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 l' containerobjet et construirait l'objet réel pour vous, qui pourrait être ObjectWthing. L'important est que vous puissiez remplacer cette règle par quelque chose qui utilisait un autre type d'objet, et votre code fonctionnerait toujours.

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

Cela serait utile lors du passage de paramètres.

Quant à votre question entre parenthèses "Comment pourriez-vous écrire une méthode qui prend 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'elle implémente tout dans IInterface, mais peu importe de quel type d'objet il s'agit, seulement qu'elle adhère au contrat, c'est-à-dire ce qu'est une interface.

Par exemple, vous êtes probablement familier avec les calculatrices et en avez probablement utilisé plusieurs au cours de vos journées, mais la plupart du temps elles sont toutes différentes. Vous, d'autre part, savez comment une calculatrice standard devrait fonctionner, vous pouvez donc toutes les utiliser, même si vous ne pouvez pas utiliser les fonctionnalités spécifiques de chaque calculatrice qu'aucune autre n'a.

C'est la beauté des interfaces. Vous pouvez écrire un morceau de code, qui sait qu'il recevra des objets dont il peut attendre certains comportements. Peu importe le type d'objet, il suffit de prendre en charge le comportement nécessaire.

Permettez-moi de vous donner un exemple concret.

Nous avons un système de traduction sur mesure pour les formulaires Windows. Ce système parcourt les contrôles d'un formulaire et traduit le texte de chacun. Le système sait comment gérer les contrôles de base, comme la propriété-type-de-contrôle-qui-a-un-texte, et des trucs de base similaires, mais pour tout élément de base, il ne suffit pas.

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

  1. Assurer la prise en charge de notre système de traduction pour détecter spécifiquement le type de contrôle avec lequel il fonctionne et traduire les bons bits (cauchemar de maintenance)
  2. Intégrer le support dans les classes de base (impossible, car 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 capacité 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.


31
Pourquoi mentionner IoC au tout début car cela ne ferait qu'ajouter plus de confusion.
Kevin Le - Khnle

1
D'accord, je dirais que la programmation contre des interfaces n'est qu'une technique pour rendre l'IoC plus facile et fiable.
terjetyl

28

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 Patterns / Gang of Four, mais il était très probablement là bien avant cela. Le concept existait certainement bien avant que Java n'existe.

La construction de l'interface Java a été créée pour aider à cette idée (entre autres), et les gens sont devenus trop concentrés sur la construction en tant que centre du sens plutôt que sur l'intention d'origine. 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 l'interface publique d'un objet ou d'un système. Ne vous inquiétez pas et ne prévoyez même pas comment il fait 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 au besoin 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, son implémentation, sans casser la façon dont les gens interagissent avec elle.

Et vous pouvez programmer à l'interface, pas à l'implémentation sans jamais utiliser une construction Interface. Vous pouvez programmer sur l'interface et non sur l'implémentation en C ++, qui n'a pas de construction 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. Les interfaces devraient toujours réagir de la même manière attendue compte tenu des mêmes paramètres d'entrée; s'il est implémenté sur l'interface et non sur l'implémentation. Le concept fonctionne dans de nombreux endroits.

Ébranlez la pensée que les interfaces Java ont quoi que ce soit à voir avec le concept de «programme à l'interface, pas la mise en œuvre». Ils peuvent aider à appliquer le concept, mais ils ne sont pas le concept.


1
La première phrase dit tout. Ce devrait être la réponse acceptée.
madumlao

14

Il semble que vous compreniez comment fonctionnent les interfaces mais que vous ne savez pas quand les utiliser ni quels avantages elles offrent. Voici quelques exemples de cas où une interface aurait un 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)
}

puis créez JpegImageLoader, GifImageLoader, PngImageLoader, etc.

La plupart des compléments et des plugins fonctionnent hors interfaces.

Une autre utilisation populaire est le modèle de référentiel. Dites que je veux charger une liste de codes postaux provenant de différentes sources

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

je pourrais ensuite créer un XMLZipCodeRepository, SQLZipCodeRepository, CSVZipCodeRepository, etc. 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 hors des 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());
    }
}

10

Cela 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 a de nouveaux composants que le client doit parfois configurer. Il existe 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 soin des différences entre les deux composants. Cependant, les classes de base n'auraient pas pu effectuer d'opérations sur ces composants si je devais me soucier des types tout le temps (enfin, ils auraient pu, mais il y aurait eu une instruction "if" ou un commutateur 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

Beaucoup d'explications là-bas, mais pour le rendre encore plus simple. Prenez par exemple a List. On peut implémenter une liste avec comme:

  1. Un tableau interne
  2. Une liste chaînée
  3. Autres implémentations

En créant une interface, dites a List. Vous ne codez que la définition de la liste ou ce que cela Listsignifie en réalité.

Vous pouvez utiliser n'importe quel type d'implémentation en interne, par exemple une arrayimplémentation. Mais supposons que vous souhaitiez modifier l'implémentation pour une raison quelconque, par exemple un bug ou des performances. Il vous suffit ensuite de modifier la déclaration List<String> ls = new ArrayList<String>()en List<String> ls = new LinkedList<String>().

Nulle part ailleurs dans le code, ne devrez-vous changer quoi que ce soit d'autre; Parce que tout le reste a été construit sur la définition de List.


8

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 sur cet ensemble d'interfaces. En théorie, vous choisissez un pilote JDBC et votre application fonctionnera simplement. Si vous découvrez qu'il existe un pilote JDBC plus rapide ou "meilleur" ou moins cher ou pour quelque raison que ce soit, vous pouvez à nouveau en théorie reconfigurer votre fichier de propriétés, et sans avoir à modifier votre application, votre application fonctionnerait toujours.


Ce n'est pas seulement utile dans le cas où un meilleur pilote devient disponible, il permet de changer complètement les fournisseurs de bases de données.
Ken Liu

3
JDBC est si mauvais qu'il doit être remplacé. Trouvez un autre exemple.
Joshua

JDBC est mauvais mais pas pour quelque raison que ce soit avec l'interface vs l'implémentation ou les niveaux d'abstraction. Et donc pour illustrer le concept en question, c'est juste parfait.
Erwin Smout

8

La programmation vers les interfaces est géniale, elle favorise un couplage lâche. Comme l'a mentionné @lassevk, l'inversion de contrôle en est une excellente utilisation.

En outre, examinez les principes SOLID . voici une série vidéo

Il passe par un codage en dur (exemple fortement couplé) puis examine les interfaces, pour enfin passer à un outil IoC / DI (NInject)


7

Je suis arrivé tardivement à cette question, mais je tiens à mentionner ici que la ligne "Programmer vers une interface, pas une implémentation" a fait l'objet d'une bonne discussion dans le livre GoF (Gang of Four) Design Patterns.

Elle a déclaré, à la p. 18:

Programmer vers une interface, pas une implémentation

Ne déclarez pas les variables comme des instances de classes concrètes particulières. Au lieu de cela, ne validez que sur une interface définie par une classe abstraite. Vous constaterez que c'est un thème commun des modèles de conception dans ce livre.

et surtout, cela a commencé par:

Il y a deux avantages à manipuler des objets uniquement en termes d'interface définie par des classes abstraites:

  1. Les clients ignorent les types spécifiques d'objets qu'ils utilisent, tant que les objets adhèrent à l'interface attendue par les clients.
  2. Les clients ne connaissent pas les classes qui implémentent ces objets. Les clients ne connaissent que la ou les classes abstraites définissant l'interface.

En d'autres termes, ne l'écrivez pas dans vos classes pour qu'il ait une quack()méthode pour les canards, puis une bark()méthode pour les chiens, car elles sont trop spécifiques pour une implémentation particulière d'une classe (ou sous-classe). Au lieu de cela, écrivez la méthode en utilisant des noms suffisamment généraux pour être utilisés dans la classe de base, tels que giveSound()ou move(), afin qu'ils puissent être utilisés pour les canards, les chiens ou même les voitures, puis le client de vos classes peut simplement dire .giveSound()plutôt que penser à utiliser quack()ou bark()même à déterminer le type avant d'émettre le bon message à envoyer à l'objet.


6

En plus de la réponse déjà sélectionnée (et des divers articles informatifs ici), je recommande fortement de prendre une copie des modèles de conception Head First . C'est une lecture très facile et répondra directement à votre question, expliquera pourquoi elle est importante et vous montrera de nombreux modèles de programmation que vous pouvez utiliser pour utiliser ce principe (et d'autres).


5

Pour ajouter aux publications existantes, le codage des interfaces aide parfois sur les grands projets lorsque les développeurs travaillent simultanément sur des composants distincts. Tout ce dont vous avez besoin est de définir des interfaces à l'avance et de leur écrire du code pendant que d'autres développeurs écrivent du code sur l'interface que vous implémentez.


4

C'est aussi bon pour les tests unitaires, vous pouvez injecter vos propres classes (qui répondent aux exigences de l'interface) dans une classe qui en dépend


4

Il peut être avantageux de programmer sur des interfaces, même lorsque nous ne dépendons pas d'abstractions.

La programmation d'interfaces nous oblige à utiliser un sous-ensemble contextuellement approprié d'un objet . Cela aide parce que:

  1. nous empêche de faire des choses contextuellement inappropriées, et
  2. nous permet de changer la mise en œuvre en toute sécurité à l'avenir.

Par exemple, considérons une Personclasse qui implémente Friendl' Employeeinterface et.

class Person implements AbstractEmployee, AbstractFriend {
}

Dans le cadre de l'anniversaire de la personne, nous programmons sur l' Friendinterface, pour éviter de traiter la personne comme une Employee.

function party() {
    const friend: Friend = new Person("Kathryn");
    friend.HaveFun();
}

Dans le cadre du travail de la personne, nous programmons sur l' Employeeinterface, pour éviter de brouiller les frontières du lieu de travail.

function workplace() {
    const employee: Employee = new Person("Kathryn");
    employee.DoWork();
}

Génial. Nous nous sommes comportés de manière appropriée dans différents contextes et notre logiciel fonctionne bien.

Dans un avenir lointain, si notre entreprise change pour travailler avec des chiens, nous pouvons changer le logiciel assez facilement. Tout d'abord, nous créons une Dogclasse qui implémente à la fois Friendet Employee. Ensuite, nous passons en toute sécurité new Person()à new Dog(). Même si les deux fonctions ont des milliers de lignes de code, cette simple modification fonctionnera car nous savons que les éléments suivants sont vrais:

  1. La fonction partyutilise uniquement le Friendsous - ensemble de Person.
  2. La fonction workplaceutilise uniquement le Employeesous - ensemble de Person.
  3. La classe Dogimplémente les interfaces Friendet Employee.

D'un autre côté, si l'un partyou l' autre workplacedevait programmer contre Person, il y aurait un risque que les deux aient un Personcode spécifique. Passer de Personà Dognous obligerait à passer au peigne fin le code pour extirper tout Personcode spécifiqueDog ne prend pas en charge.

Moralité : la programmation des interfaces aide notre code à se comporter correctement et à être prêt pour le changement. Il prépare également notre code à dépendre des abstractions, ce qui apporte encore plus d'avantages.


1
En supposant que vous n'avez pas d'interfaces excessivement larges, c'est-à-dire.
Casey

4

Si j'écris une nouvelle classe Swimmerpour ajouter la fonctionnalité swim()et besoin d'utiliser un objet de classe disons Dog, et cette Dogclasse implémente l'interface Animalqui déclare swim().

En haut de la hiérarchie ( Animal), c'est très abstrait tandis qu'en bas ( Dog) c'est très concret. La façon dont je pense à la «programmation vers les interfaces» est que, lorsque j'écris une Swimmerclasse, je veux écrire mon code par rapport à l'interface qui est aussi loin dans cette hiérarchie qui dans ce cas est un Animalobjet. Une interface est exempte de détails d'implémentation et rend ainsi votre code faiblement couplé.

Les détails de l'implémentation peuvent être modifiés avec le temps, cependant, cela n'affectera pas le code restant puisque tout ce avec quoi vous interagissez est avec l'interface et non l'implémentation. Vous ne vous souciez pas de l'implémentation ... tout ce que vous savez, c'est qu'il y aura une classe qui implémenterait l'interface.


3

Donc, juste pour bien comprendre, l'avantage d'une interface est que je peux séparer l'appel d'une méthode de n'importe quelle classe particulière. Au lieu de cela, créez une instance de l'interface, où l'implémentation est donnée à partir de la classe que je choisis qui implémente cette interface. Cela me permet donc d'avoir de nombreuses classes, qui ont des fonctionnalités similaires mais légèrement différentes et dans certains cas (les cas liés à l'intention de l'interface) ne se soucient pas de quel objet il s'agit.

Par exemple, je pourrais avoir une interface de mouvement. Une méthode qui fait «bouger» quelque chose et tout objet (personne, voiture, chat) qui implémente l'interface de mouvement pourrait être transmise et ordonnée de bouger. Sans la méthode, tout le monde connaît le type de classe.


3

Imaginez que vous ayez un produit appelé «Zebra» qui peut être étendu par des plugins. Il trouve les plugins en recherchant des DLL dans certains répertoires. Il charge toutes ces DLL et utilise la réflexion pour trouver toutes les classes qui implémententIZebraPlugin , puis appelle les méthodes de cette interface pour communiquer avec les plugins.

Cela le rend complètement indépendant de toute classe de plugin spécifique - peu importe ce que sont les classes. Il se soucie seulement qu'ils remplissent les spécifications de l'interface.

Les interfaces sont un moyen de définir des points d'extensibilité comme celui-ci. Le code qui parle à une interface est couplé de manière plus lâche - en fait, il n'est pas du tout couplé à un autre code spécifique. Il peut interagir avec des plugins écrits des années plus tard par des personnes qui n'ont jamais rencontré le développeur d'origine.

Vous pouvez à la place utiliser une classe de base avec des fonctions virtuelles - tous les plugins seront dérivés de la classe de base. Mais cela est beaucoup plus limitatif car une classe ne peut avoir qu'une seule classe de base, alors qu'elle peut implémenter n'importe quel nombre d'interfaces.


3

Explication C ++.

Considérez une interface comme vos méthodes publiques de classe.

Vous pouvez ensuite créer un modèle qui «dépend» de ces méthodes publiques afin d'exécuter sa propre fonction (il rend les appels de fonction définis dans l'interface publique des classes). Disons que ce modèle est un conteneur, comme une classe Vector, et l'interface dont il dépend est un algorithme de recherche.

Toute classe d'algorithme qui définit les fonctions / interface que Vector appelle pour satisfaire le «contrat» (comme quelqu'un l'a expliqué dans la réponse d'origine). Les algorithmes n'ont même pas besoin d'être de la même classe de base; la seule exigence est que les fonctions / méthodes dont dépend le vecteur (interface) soient définies dans votre algorithme.

Le point de tout cela est que vous pouvez fournir n'importe quel algorithme / classe de recherche différent tant qu'il fournit l'interface dont Vector dépend (recherche de bulles, recherche séquentielle, recherche rapide).

Vous pouvez également vouloir concevoir d'autres conteneurs (listes, files d'attente) qui exploiteraient le même algorithme de recherche que Vector en les faisant remplir l'interface / le contrat dont dépendent vos algorithmes de recherche.

Cela vous fait gagner du temps (principe de réutilisation du code OOP) car vous pouvez écrire un algorithme une fois au lieu de plusieurs fois spécifique à chaque nouvel objet que vous créez sans trop compliquer le problème avec une arborescence d'héritage envahie.

Quant à «manquer» la façon dont les choses fonctionnent; big-time (au moins en C ++), car c'est ainsi que la plupart du framework de la bibliothèque standard TEMPLATE fonctionne.

Bien sûr, lorsque vous utilisez l'héritage et les classes abstraites, la méthodologie de programmation d'une interface change; mais le principe est le même, vos fonctions / méthodes publiques sont votre interface de classes.

C'est un sujet énorme et l'un des principes fondamentaux des modèles de conception.


3

En Java, ces classes concrètes implémentent toutes l'interface CharSequence:

CharBuffer, String, StringBuffer, StringBuilder

Ces classes concrètes n'ont pas de classe parente commune autre que Object, donc il n'y a rien qui les relie, à part le fait qu'elles ont chacune quelque chose à voir avec des tableaux de caractères, représentant ou manipulant tel. Par exemple, les caractères de String ne peuvent pas être modifiés une fois qu'un objet String est instancié, tandis que les caractères de StringBuffer ou StringBuilder peuvent être modifiés.

Pourtant, chacune de ces classes est capable d'implémenter convenablement les méthodes d'interface CharSequence:

char charAt(int index)
int length()
CharSequence subSequence(int start, int end)
String toString()

Dans certains cas, les classes de bibliothèque de classes Java qui acceptaient autrefois String ont été révisées pour accepter maintenant l'interface CharSequence. Donc, si vous avez une instance de StringBuilder, au lieu d'extraire un objet String (ce qui signifie instancier une nouvelle instance d'objet), il peut simplement passer le StringBuilder lui-même lors de l'implémentation de l'interface CharSequence.

L'interface Appendable que certaines classes implémentent a à peu près le même type d'avantage pour toute situation où des caractères peuvent être ajoutés à une instance de l'instance d'objet de classe concrète sous-jacente. Toutes ces classes concrètes implémentent l'interface Appendable:

BufferedWriter, CharArrayWriter, CharBuffer, FileWriter, FilterWriter, LogStream, OutputStreamWriter, PipedWriter, PrintStream, PrintWriter, StringBuffer, StringBuilder, StringWriter, Writer


C'est dommage que les interfaces CharSequencesoient si anémiques. Je souhaite que Java et .NET aient permis aux interfaces d'avoir une implémentation par défaut, afin que les gens ne réduisent pas les interfaces uniquement dans le but de minimiser le code standard. Étant donné toute CharSequenceimplémentation légitime, on pourrait émuler la plupart des fonctions en Stringutilisant uniquement les quatre méthodes ci-dessus, mais de nombreuses implémentations pourraient exécuter ces fonctions de manière beaucoup plus efficace par d'autres moyens. Malheureusement, même si une implémentation particulière de CharSequencecontient tout en un seul char[]et pourrait en exécuter plusieurs ...
supercat

... opérations comme indexOfrapidement, il n'y a aucun moyen qu'un appelant qui n'est pas familier avec une implémentation particulière CharSequencepuisse lui demander de le faire plutôt que d'avoir à utiliser charAtpour examiner chaque caractère individuel.
supercat

3

Petite histoire: Un facteur est prié de rentrer chez lui après la maison et de recevoir les couvertures (lettres, documents, chèques, cartes-cadeaux, application, lettre d'amour) avec l'adresse écrite dessus pour la livraison.

Supposons qu'il n'y ait pas de couverture et demandez au facteur de rentrer chez lui après la maison et de recevoir toutes les choses et de livrer à d'autres personnes, le facteur peut devenir confus.

Il vaut donc mieux l'envelopper avec une couverture (dans notre histoire, c'est l'interface), alors il fera bien son travail.

Maintenant, le travail du facteur est de recevoir et de livrer les couvertures uniquement (il ne dérangerait pas ce qu'il y a à l'intérieur de la couverture).

Créez un type de type interfacenon réel, mais implémentez-le avec le type réel.

Créer à l'interface signifie que vos composants s'intègrent facilement dans le reste du code

Je vous donne un exemple.

vous avez l'interface AirPlane comme ci-dessous.

interface Airplane{
    parkPlane();
    servicePlane();
}

Supposons que vous ayez des méthodes dans votre classe de contrôleur comme

parkPlane(Airplane plane)

et

servicePlane(Airplane plane)

mis en œuvre dans votre programme. Cela ne cassera pas votre code. Je veux dire, il n'a pas besoin de changer tant qu'il accepte les arguments AirPlane.

Parce qu'il acceptera tout avion malgré type réel, flyer, highflyr, fighter, etc.

Aussi, dans une collection:

List<Airplane> plane; // Prendra tous vos avions.

L'exemple suivant clarifiera votre compréhension.


Vous avez un avion de chasse qui le met en œuvre, donc

public class Fighter implements Airplane {

    public void  parkPlane(){
        // Specific implementations for fighter plane to park
    }
    public void  servicePlane(){
        // Specific implementatoins for fighter plane to service.
    }
}

La même chose pour HighFlyer et autres clasess:

public class HighFlyer implements Airplane {

    public void  parkPlane(){
        // Specific implementations for HighFlyer plane to park
    }

    public void  servicePlane(){
        // specific implementatoins for HighFlyer plane to service.
    }
}

Pensez maintenant à vos classes de contrôleur en utilisant AirPlaneplusieurs fois,

Supposons que votre classe Controller soit ControlPlane comme ci-dessous,

public Class ControlPlane{ 
 AirPlane plane;
 // so much method with AirPlane reference are used here...
}

Ici, la magie vient car vous pouvez créer AirPlaneautant d'instances que vous le souhaitez et vous ne changez pas le code de la ControlPlaneclasse.

Vous pouvez ajouter une instance ...

JumboJetPlane // implementing AirPlane interface.
AirBus        // implementing AirPlane interface.

Vous pouvez également supprimer des instances de types créés précédemment.


2

Une interface est comme un contrat, où vous voulez que votre classe d'implémentation implémente des méthodes écrites dans le contrat (interface). Étant donné que Java ne fournit pas d'héritage multiple, la «programmation vers l'interface» est un bon moyen de réaliser l'héritage multiple.

Si vous avez une classe A qui étend déjà une autre classe B, mais que vous souhaitez que cette classe A suive également certaines directives ou implémente un certain contrat, vous pouvez le faire par la stratégie de "programmation pour l'interface".


2

Q: - ... "Pourriez-vous utiliser n'importe quelle classe qui implémente une interface?"
R: - Oui.

Q: - ... "Quand auriez-vous besoin de faire ça?"
R: - Chaque fois que vous avez besoin d'une ou de classes qui implémentent des interfaces.

Remarque: Nous n'avons pas pu instancier une interface non implémentée par une classe - True.

  • Pourquoi?
  • Parce que l'interface n'a que des prototypes de méthodes, pas des définitions (juste des noms de fonctions, pas leur logique)

AnIntf anInst = new Aclass();
// nous ne pourrions le faire que si Aclass implémente AnIntf.
// anInst aura une référence Aclass.


Remarque: Maintenant, nous pouvions comprendre ce qui se passait si Bclass et Cclass implémentaient le même Dintf.

Dintf bInst = new Bclass();  
// now we could call all Dintf functions implemented (defined) in Bclass.

Dintf cInst = new Cclass();  
// now we could call all Dintf functions implemented (defined) in Cclass.

Ce que nous avons: mêmes prototypes d'interface (noms de fonctions dans l'interface), et appeler différentes implémentations.

Bibliographie: prototypes - wikipedia


1

Le programme vers une interface permet de modifier la mise en œuvre du contrat défini par l'interface de manière transparente. Il permet un couplage lâche entre contrat et implémentations spécifiques.

IInterface classRef = new ObjectWhatever()

Vous pourriez utiliser n'importe quelle classe qui implémente IInterface? Quand auriez-vous besoin de faire cela?

Jetez un oeil à cette question SE pour un bon exemple.

Pourquoi l'interface pour une classe Java devrait-elle être préférée?

l'utilisation d'une interface affecte-t-elle les performances?

si oui combien?

Oui. Il aura une légère surcharge de performances en moins de secondes. Mais si votre application doit modifier dynamiquement l'implémentation de l'interface, ne vous inquiétez pas de l'impact sur les performances.

comment l'éviter sans avoir à conserver deux bits de code?

N'essayez pas d'éviter plusieurs implémentations d'interface si votre application en a besoin. En l'absence de couplage étroit de l'interface avec une implémentation spécifique, vous devrez peut-être déployer le correctif pour changer une implémentation en une autre implémentation.

Un bon cas d'utilisation: mise en œuvre du modèle de stratégie:

Exemple réel du modèle de stratégie


1

programme à une interface est un terme du livre GOF. je ne dirais pas directement que cela a à voir avec l'interface java mais plutôt avec de vraies interfaces. pour réaliser une séparation nette des couches, vous devez créer une séparation entre les systèmes, par exemple: supposons que vous ayez une base de données concrète que vous souhaitez utiliser, vous ne "programmez jamais à la base de données", au lieu de cela, vous "programmez à l'interface de stockage". De même, vous ne programmez jamais vers un service Web, mais vous programmez plutôt vers une «interface client». c'est pour que vous puissiez facilement échanger les choses.

je trouve que ces règles m'aident:

1 . nous utilisons une interface java lorsque nous avons plusieurs types d'objet. si je n'ai qu'un seul objet, je ne vois pas le point. s'il y a au moins deux implémentations concrètes d'une idée, j'utiliserais une interface java.

2 . si, comme je l'ai indiqué ci-dessus, vous souhaitez apporter le découplage d'un système externe (système de stockage) à votre propre système (base de données locale), utilisez également une interface.

remarquez qu'il y a deux façons de considérer quand les utiliser. J'espère que cela t'aides.


0

Je vois également beaucoup de bonnes réponses explicatives ici, donc je veux donner mon point de vue ici, y compris des informations supplémentaires sur ce que j'ai remarqué lors de l'utilisation de cette méthode.

Tests unitaires

Au cours des deux dernières années, j'ai écrit un projet de loisir et je n'ai pas fait de tests unitaires pour cela. Après avoir écrit environ 50 000 lignes, j'ai découvert qu'il serait vraiment nécessaire d'écrire des tests unitaires. Je n'ai pas utilisé d'interfaces (ou avec parcimonie) ... et quand j'ai fait mon premier test unitaire, j'ai découvert que c'était compliqué. Pourquoi?

Parce que je devais faire beaucoup d'instances de classe, utilisées pour l'entrée comme variables de classe et / ou paramètres. Les tests ressemblent donc plus à des tests d'intégration (devoir faire un «framework» complet de classes car tout était lié).

Peur des interfaces J'ai donc décidé d'utiliser des interfaces. Ma crainte était que je devais implémenter toutes les fonctionnalités partout (dans toutes les classes utilisées) plusieurs fois. D'une certaine manière, cela est vrai, cependant, en utilisant l'héritage, il peut être considérablement réduit.

Combinaison d'interfaces et d'héritage J'ai découvert que la combinaison est très bonne à utiliser. Je donne un exemple très simple.

public interface IPricable
{
    int Price { get; }
}

public interface ICar : IPricable

public abstract class Article
{
    public int Price { get { return ... } }
}

public class Car : Article, ICar
{
    // Price does not need to be defined here
}

De cette façon, la copie de code n'est pas nécessaire, tout en ayant l'avantage d'utiliser une voiture comme interface (ICar).


0

Commençons par quelques définitions d'abord:

Interface n. L'ensemble de toutes les signatures définies par les opérations d'un objet est appelé l'interface de l'objet

Tapez n. Une interface particulière

Un exemple simple d'une interface de telle que définie ci - dessus serait toutes les méthodes d'objet PDO tels que query(), commit(),close() etc., dans son ensemble, et non séparément. Ces méthodes, c'est-à-dire son interface définissent l'ensemble complet des messages, requêtes qui peuvent être envoyées à l'objet.

Un type tel que défini ci-dessus est une interface particulière. Je vais utiliser l'interface de forme maquillée pour démontrer: draw(), getArea(), getPerimeter()etc ..

Si un objet est du type Base de données, nous voulons dire qu'il accepte les messages / requêtes de l'interface de base de données query(), commit()etc. Les objets peuvent être de plusieurs types. Vous pouvez faire en sorte qu'un objet de base de données soit du type forme tant qu'il implémente son interface, auquel cas il s'agirait de sous-typage .

De nombreux objets peuvent être de nombreuses interfaces / types différents et implémenter cette interface différemment. Cela nous permet de substituer des objets, nous permettant de choisir lequel utiliser. Également connu sous le nom de polymorphisme.

Le client ne connaîtra que l'interface et non l'implémentation.

Donc, en substance, la programmation d'une interface impliquerait de faire un certain type de classe abstraite comme Shapeavec l'interface spécifiée uniquement draw(), c'est-à - dire getCoordinates(), getArea()etc. D'où un programme vers une interface et non une implémentation.


0

"Programmer vers l'interface" signifie ne pas fournir de code en dur, ce qui signifie que votre code doit être étendu sans casser la fonctionnalité précédente. Juste des extensions, pas la modification du code précédent.

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.