J'ai souvent rencontré le terme "programmation vers une interface au lieu d'une implémentation", et je pense que je comprends un peu ce que cela signifie. Mais je veux m'assurer de bien comprendre ses avantages et ses implémentations possibles.
«Programmation vers une interface» signifie que, lorsque cela est possible, il convient de se référer à un niveau plus abstrait d'une classe (une interface, une classe abstraite ou parfois une superclasse quelconque), au lieu de se référer à une implémentation concrète.
Un exemple courant en Java est d'utiliser:
List myList = new ArrayList();
au lieu de ArrayList myList = new ArrayList();
.
J'ai deux questions à ce sujet:
Je veux m'assurer de bien comprendre les principaux avantages de cette approche. Je pense que les avantages sont surtout la flexibilité. Déclarer un objet comme une référence de plus haut niveau, plutôt qu'une implémentation concrète, permet plus de flexibilité et de maintenabilité tout au long du cycle de développement et tout au long du code. Est-ce correct? La flexibilité est-elle le principal avantage?
Existe-t-il d'autres moyens de «programmer vers une interface»? Ou est-ce que «déclarer une variable comme une interface plutôt qu'une implémentation concrète» est la seule implémentation de ce concept?
Je ne parle pas de l'interface de construction Java . Je parle du principe OO "programmation vers une interface, pas une implémentation". Dans ce principe, l '"interface" mondiale fait référence à tout "supertype" d'une classe - une interface, une classe abstraite ou une superclasse simple qui est plus abstraite et moins concrète que ses sous-classes plus concrètes.
la source
Réponses:
Ce n'est pas correct . Ou du moins, ce n'est pas tout à fait correct.
Le point le plus important vient du point de vue de la conception du programme. Ici, «programmer vers une interface» signifie concentrer votre conception sur ce que fait le code, pas sur la façon dont il le fait. Il s'agit d'une distinction essentielle qui pousse votre conception vers l'exactitude et la flexibilité.
L'idée principale est que les domaines changent beaucoup plus lentement que les logiciels. Disons que vous disposez d'un logiciel pour garder une trace de votre liste d'épicerie. Dans les années 80, ce logiciel fonctionnait avec une ligne de commande et certains fichiers plats sur disquette. Ensuite, vous avez une interface utilisateur. Ensuite, vous pouvez peut-être mettre la liste dans la base de données. Plus tard, il peut être déplacé vers le cloud, les téléphones mobiles ou l'intégration Facebook.
Si vous avez conçu votre code spécifiquement autour de l'implémentation (disquettes et lignes de commande), vous seriez mal préparé aux changements. Si vous avez conçu votre code autour de l'interface (en manipulant une liste d'épicerie), l'implémentation est libre de changer.
la source
List myList = new ArrayList()
au lieu deArrayList myList = new ArrayList()
. (La question est dans le commentaire suivant)List
/ ceArrayList
n'est pas du tout ce dont je parle. Cela ressemble plus à la fourniture d'unEmployee
objet plutôt qu'à l'ensemble de tables liées que vous utilisez pour stocker un enregistrement Employé. Ou fournir une interface pour parcourir les chansons, et ne pas se soucier si ces chansons sont mélangées, ou sur un CD, ou en streaming à partir d'Internet. Ce ne sont que des séquences de chansons.Ma compréhension de la «programmation vers une interface» est différente de ce que la question ou les autres réponses suggèrent. Ce qui ne veut pas dire que ma compréhension est correcte, ou que les choses dans les autres réponses ne sont pas de bonnes idées, juste qu'elles ne sont pas ce à quoi je pense quand j'entends ce terme.
La programmation vers une interface signifie que lorsque vous êtes présenté avec une interface de programmation (que ce soit une bibliothèque de classes, un ensemble de fonctions, un protocole réseau ou autre), vous continuez à n'utiliser que des choses garanties par l'interface. Vous pouvez avoir des connaissances sur l'implémentation sous-jacente (vous l'avez peut-être écrite), mais vous ne devriez jamais utiliser ces connaissances.
Par exemple, supposons que l'API vous présente une valeur opaque qui est une "poignée" vers quelque chose interne. Vos connaissances peuvent vous dire que ce descripteur est vraiment un pointeur, et vous pouvez le déréférencer et accéder à une valeur, ce qui pourrait vous permettre d'accomplir facilement une tâche que vous souhaitez effectuer. Mais l'interface ne vous offre pas cette option; c'est votre connaissance de l'implémentation particulière qui le fait.
Le problème avec cela est qu'il crée un couplage fort entre votre code et l'implémentation, exactement ce que l'interface était censée empêcher. Selon la politique, cela peut signifier que l'implémentation ne peut plus être modifiée, car cela casserait votre code, ou que votre code est très fragile et continue de casser à chaque mise à niveau ou changement de l'implémentation sous-jacente.
Un grand exemple de ceci est les programmes écrits pour Windows. WinAPI est une interface, mais de nombreuses personnes ont utilisé des astuces qui fonctionnaient en raison de l'implémentation particulière de Windows 95, par exemple. Ces astuces ont peut-être rendu leurs programmes plus rapides ou leur ont permis de faire des choses avec moins de code que cela n'aurait été nécessaire autrement. Mais ces astuces signifiaient également que le programme plantait sur Windows 2000, car l'API y était implémentée différemment. Si le programme était suffisamment important, Microsoft pourrait en fait aller de l'avant et ajouter un peu de hack à leur implémentation pour que le programme continue de fonctionner, mais le coût de cela est une complexité accrue (avec tous les problèmes qui en découlent) du code Windows. Cela rend également la vie très difficile pour les gens de Wine, car ils essaient également d'implémenter WinAPI, mais ils ne peuvent se référer qu'à la documentation pour savoir comment le faire,
la source
Je ne peux que parler de mon expérience personnelle, car cela ne m'a jamais été formellement enseigné non plus.
Votre premier point est correct. La flexibilité acquise vient du fait de ne pas pouvoir appeler accidentellement les détails d'implémentation de la classe concrète où ils ne devraient pas être appelés.
Par exemple, considérons une
ILogger
interface qui est actuellement implémentée en tant queLogToEmailLogger
classe concrète . LaLogToEmailLogger
classe expose toutes lesILogger
méthodes et propriétés, mais se trouve également avoir une propriété spécifique à l'implémentationsysAdminEmail
.Lorsque votre enregistreur est utilisé dans votre application, il ne devrait pas être du ressort du code consommateur de définir le
sysAdminEmail
. Cette propriété doit être définie lors de la configuration de l'enregistreur et doit être cachée au monde.Si vous codiez par rapport à l'implémentation concrète, vous pourriez accidentellement définir la propriété d'implémentation lors de l'utilisation de l'enregistreur. Maintenant, votre code d'application est étroitement couplé à votre enregistreur, et le passage à un autre enregistreur nécessitera d'abord de découpler votre code de celui d'origine.
En ce sens, le codage sur une interface desserre le couplage .
Concernant votre deuxième point: Une autre raison que j'ai vue pour le codage sur une interface est de réduire la complexité du code.
Par exemple, imaginez que j'ai un jeu avec les interfaces suivantes
I2DRenderable
,I3DRenderable
,IUpdateable
. Il n'est pas rare qu'un seul composant de jeu ait à la fois du contenu de rendu 2D et 3D. D'autres composants peuvent être uniquement 2D et d'autres uniquement 3D.Si le rendu 2D est effectué par un module, il est alors logique qu'il conserve une collection de
I2DRenderable
s. Peu importe si les objets de sa collection sont égalementI3DRenderable
ouIUpdateble
comme d'autres modules seront chargés de traiter ces aspects des objets.Le stockage des objets pouvant être rendus sous forme de liste
I2DRenderable
réduit la complexité de la classe de rendu. La logique de rendu et de mise à jour 3D n'est pas son souci et donc ces aspects de ses objets enfants peuvent et doivent être ignorés.En ce sens, le codage sur une interface maintient une complexité faible en isolant les problèmes .
la source
Il y a peut-être deux utilisations du mot interface utilisé ici. L'interface à laquelle vous faites principalement référence dans votre question est une interface Java . C'est spécifiquement un concept Java, plus généralement c'est une interface de langage de programmation.
Je dirais que la programmation vers une interface est un concept plus large. Les API REST désormais populaires qui sont disponibles pour de nombreux sites Web sont un autre exemple du concept plus large de programmation d'une interface à un niveau supérieur. En créant une couche entre le fonctionnement interne de votre code et le monde extérieur (personnes sur Internet, autres programmes, voire d'autres parties du même programme), vous pouvez changer quoi que ce soit à l'intérieur de votre code tant que vous ne changez pas ce que le monde extérieur attend, lorsque cela est défini par une interface ou un contrat que vous avez l'intention d'honorer.
Cela vous donne alors la flexibilité de refactoriser votre code interne sans avoir à dire toutes les autres choses qui dépendent de son interface.
Cela signifie également que votre code devrait être plus stable. En vous en tenant à l'interface, vous ne devriez pas casser le code des autres. Lorsque vous devez vraiment changer l'interface, vous pouvez publier une nouvelle version majeure (1.abc à 2.xyz) de l'API qui signale qu'il y a des changements de rupture dans l'interface de la nouvelle version.
Comme le souligne @Doval dans les commentaires sur cette réponse, il existe également une localité d'erreurs. Je pense que tout cela se résume à l'encapsulation. Tout comme vous l'utiliseriez pour des objets dans une conception orientée objet, ce concept est également utile à un niveau supérieur.
la source
Une analogie dans le monde réel pourrait aider:
Une prise électrique secteur dans une interface.
Oui; cette chose à trois broches à l'extrémité du cordon d'alimentation de votre téléviseur, radio, aspirateur, lave-linge, etc.
Tout appareil qui a une prise principale (c'est-à-dire qui met en œuvre l'interface "a une prise secteur") peut être traité exactement de la même manière; ils peuvent tous être branchés sur une prise murale et peuvent tirer de l'énergie de cette prise.
Ce que chaque appareil n'est tout à fait différent. Vous n'allez pas aller très loin en nettoyant vos tapis avec le téléviseur et la plupart des gens ne regardent pas leur machine à laver pour se divertir. Mais tous ces appareils partagent le même comportement de pouvoir être branchés sur une prise murale.
C'est ce que les interfaces vous procurent. Comportements
unifiés qui peuvent être effectués par de nombreuses classes d'objets différentes, sans avoir besoin des complications de l'héritage.
la source
Le terme «programmation vers une interface» est ouvert à de nombreuses interprétations. Interface dans le développement de logiciels est un mot très courant. Voici comment j'explique le concept aux développeurs juniors que j'ai formés au fil des ans.
Dans l'architecture logicielle, il existe un large éventail de frontières naturelles. Les exemples courants incluent
Ce qui importe, c'est que lorsque ces frontières naturelles existent, elles sont identifiées et le contrat de comportement de cette frontière est spécifié. Vous testez votre logiciel non pas par le comportement de "l'autre côté", mais par la conformité de vos interactions avec les spécifications.
Les conséquences de ceci sont:
Bien qu'une grande partie de cela puisse concerner les classes et les interfaces, il est tout aussi important de réaliser que cela concerne également les modèles de données, les protocoles réseau et, de manière plus générale, le travail avec plusieurs développeurs.
la source