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 que je comprends 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 bien "déclarer une variable comme une interface plutôt qu'une implémentation concrète" est-il la seule implémentation de ce concept?
Je suis ne parle pas de la Java. Je parle de la programmation OO ") d'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 ce sont des sous-classes plus concrètes.
"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.
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 sur une interface" signifie concentrer votre conception sur quoi le code fait, pas comment 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.
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" pour 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 que se référer à la documentation pour savoir comment faire, ce qui conduit à de nombreux programmes qui ne fonctionnent pas comme ils le devraient parce qu'ils (accidentellement ou intentionnellement) s'appuyer sur certains détails de mise en œuvre.
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 invoqués.
Par exemple, considérons une interface ILogger
qui est actuellement implémentée en tant que classe concrète LogToEmailLogger
. La classe LogToEmailLogger
expose toutes les méthodes et propriétés ILogger
, mais se trouve également avoir une propriété spécifique à l'implémentation sysAdminEmail
.
Lorsque votre enregistreur est utilisé dans votre application, le code consommateur ne devrait pas avoir à définir 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, coder vers 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 logique qu'il conserve une collection de I2DRenderable
s. Peu importe si les objets de sa collection sont également I3DRenderable
ou IUpdateble
car d'autres modules seront chargés de traiter ces aspects des objets.
Stockage des objets pouvant être rendus dans une liste de 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 par isoler les problèmes.
Il y a peut-être deux utilisations de l'interface Word utilisées ici. L'interface à laquelle vous faites principalement référence dans votre question est un Java Interface . Il s'agit spécifiquement d'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 maintenant 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 l'extérieur monde (personnes sur Internet, autres programmes, même 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, où 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.a.b.c à 2.x.y.z) 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.
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 fait chaque appareil individuel est entièrement 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 pouvant être exécutés par plusieurs différentes classes d'objets, sans avoir besoin des complications de l'héritage.
Le terme "programmation vers une interface" est ouvert à de nombreuses interprétations. L'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.