N’est-ce pas le but d’une interface que plusieurs classes adhèrent à un ensemble de règles et d’implémentations?
design-patterns
coding-standards
interfaces
Lamin Sanneh
la source
la source
Réponses:
A strictement parler, non, YAGNI s'applique. Cela dit, le temps que vous passerez à créer l'interface est minime, en particulier si vous disposez d'un outil de génération de code pratique qui effectue la majeure partie du travail à votre place. Si vous ne savez pas si vous allez avoir besoin de l'interface ou non, je dirais qu'il est préférable de privilégier la définition d'une interface.
De plus, l'utilisation d'une interface, même pour une seule classe, vous fournira une autre implémentation fictive pour les tests unitaires, qui n'est pas en production. La réponse d'Avner Shahar-Kashtan s'étend sur ce point.
la source
Je répondrais que si vous avez besoin d'une interface ou non ne dépend pas du nombre de classes qui vont l'implémenter. Les interfaces sont un outil permettant de définir des contrats entre plusieurs sous-systèmes de votre application. Donc, ce qui compte vraiment, c'est la façon dont votre application est divisée en sous-systèmes. Il devrait y avoir des interfaces comme interface frontale pour les sous-systèmes encapsulés, quel que soit le nombre de classes qui les implémentent.
Voici une règle de base très utile:
Foo
référer directement à la classeBarImpl
, vous vous engagez fermement à changerFoo
chaque fois que vous changezBarImpl
. En gros, vous les traitez comme une seule unité de code divisée en deux classes.Foo
référence à l' interfaceBar
, vous vous engagez plutôt à éviter les changementsFoo
lorsque vous changezBarImpl
.Si vous définissez des interfaces aux points clés de votre application, vous réfléchissez bien aux méthodes qu’elles doivent prendre en charge et qu’elles ne doivent pas, et vous les commentez clairement pour décrire comment une implémentation est censée se comporter (et comment), votre demande sera beaucoup plus facile à comprendre , car ces interfaces commentaires fourniront une sorte de cahier des charges de l'application une description de la façon dont il est destiné à se comporter. Cela facilite beaucoup la lecture du code (au lieu de demander "qu'est-ce que ce code est censé faire", vous pouvez demander "comment ce code fait-il ce qu'il est censé faire").
En plus de tout cela (ou à cause de cela), les interfaces favorisent une compilation séparée. Comme les interfaces sont faciles à compiler et ont moins de dépendances que leurs implémentations, cela signifie que si vous écrivez en classe
Foo
pour utiliser une interfaceBar
, vous pouvez généralement recompilerBarImpl
sans avoir besoin de recompilerFoo
. Dans les grandes applications, cela peut faire gagner beaucoup de temps.la source
Foo
dépend de l'interface,Bar
elleBarImpl
peut être modifiée sans avoir à recompilerFoo
. 4. Contrôle d'accès plus fin que les offres publiques / privées (exposer la même classe à deux clients via des interfaces différentes).If you make class Foo refer directly to class BarImpl, you're strongly committing yourself to change Foo every time you change BarImpl
Lors du changement de BarImpl, quels sont les changements qui peuvent être évités dans Foo en utilisantinterface Bar
? Puisque tant que les signatures et les fonctionnalités d'une méthode ne changent pas dans BarImpl, Foo ne nécessitera pas de changement, même sans interface (tout le but de l'interface). Je parle du scénario où seulement une classeBarImpl
implémente Bar. Pour un scénario à plusieurs classes, je comprends le principe d’inversion de dépendance et l’utilité de son interface.Les interfaces sont désignées pour définir un comportement, c'est-à-dire un ensemble de prototypes de fonctions / méthodes. Les types implémentant l'interface implémenteront ce comportement. Ainsi, lorsque vous traitez avec un tel type, vous savez (en partie) quel comportement il a.
Il n'est pas nécessaire de définir une interface si vous savez que le comportement défini par celle-ci ne sera utilisé qu'une seule fois. KISS (restez simple, stupide)
la source
Bien qu'en théorie, vous ne devriez pas avoir une interface juste pour avoir une interface, la réponse de Yannis Rizos laisse entrevoir d' autres complications:
Lorsque vous écrivez des tests unitaires et utilisez des frameworks factices tels que Moq ou FakeItEasy (pour nommer les deux plus récents que j'ai utilisés), vous créez implicitement une autre classe qui implémente l'interface. La recherche dans le code ou l'analyse statique peut prétendre qu'il n'y a qu'une seule implémentation, mais en réalité il y a l'implémentation interne. Chaque fois que vous commencez à écrire des simulacres, vous constaterez que l'extraction d'interfaces est logique.
Mais attendez, il y a plus. Il y a plus de scénarios où il y a des implémentations d'interface implicites. L'utilisation de la pile de communication WCF .NET, par exemple, génère un proxy pour un service distant, qui implémente à nouveau l'interface.
Dans un environnement de code propre, je suis d'accord avec le reste des réponses ici. Cependant, faites attention à tous les frameworks, patterns ou dépendances que vous pourriez utiliser qui pourraient utiliser des interfaces.
la source
Non, vous n'en avez pas besoin et je considère comme un anti-motif de créer automatiquement des interfaces pour chaque référence de classe.
Fabriquer Foo / FooImpl pour tout est très coûteux. L'EDI peut créer l'interface / implémentation gratuitement, mais lorsque vous naviguez dans le code, vous disposez de la charge cognitive supplémentaire de F3 / F12 pour
foo.doSomething()
vous amener à la signature d'interface et non de l'implémentation réelle souhaitée. De plus, vous avez le fouillis de deux fichiers au lieu d'un pour tout.Vous ne devriez donc le faire que lorsque vous en avez réellement besoin pour quelque chose.
Abordons maintenant les contre-arguments:
J'ai besoin d'interfaces pour les infrastructures d'injection de dépendance
Les interfaces pour supporter les frameworks sont héritées. En Java, les interfaces étaient un préalable pour les mandataires dynamiques, pré-CGLIB. Aujourd'hui, vous n'en avez généralement pas besoin. C'est considéré comme un progrès et un avantage pour la productivité des développeurs que vous n'en ayez plus besoin dans EJB3, Spring, etc.
J'ai besoin de simulacres pour les tests unitaires
Si vous écrivez vous-même et que vous avez deux implémentations réelles, une interface est appropriée. Nous n'aurions probablement pas cette discussion en premier lieu si votre base de code avait à la fois un FooImpl et un TestFoo.
Mais si vous utilisez un framework moqueur comme Moq, EasyMock ou Mockito, vous pouvez vous moquer des classes sans avoir besoin d'interfaces. Cela revient à définir
foo.method = mockImplementation
dans un langage dynamique où des méthodes peuvent être assignées.Nous avons besoin d'interfaces pour suivre le principe d'inversion de dépendance (DIP)
DIP indique que vous construisez pour dépendre de contrats (interfaces) et non de mises en œuvre. Mais une classe est déjà un contrat et une abstraction. C'est à cela que servent les mots clés publics / privés. À l'université, l'exemple canonique ressemblait à une classe Matrix ou Polynomial. Les consommateurs disposent d'une API publique pour créer, ajouter des matrices, etc. Aucune matrice ni MatrixImpl n’était requise pour prouver ce point.
De plus, le DIP est souvent sur-appliqué à chaque niveau d'appel de classe / méthode, pas seulement aux limites des modules principaux. Un signe que vous appliquez trop le DIP, c'est que votre interface et votre implémentation changent en phase de verrouillage, de sorte que vous devez toucher deux fichiers pour effectuer un changement au lieu d'un. Si DIP est appliqué correctement, cela signifie que votre interface ne devrait pas avoir à changer souvent. En outre, un autre signe est que votre interface n'a qu'un seul consommateur réel (sa propre application). Histoire différente si vous construisez une bibliothèque de classes pour une utilisation dans de nombreuses applications différentes.
Ceci est un corollaire à l'observation de l'oncle Bob Martin au sujet de la raillerie - vous devriez seulement avoir besoin de vous moquer des limites architecturales majeures. Dans une application Web, l’accès HTTP et DB est la limite principale. Tous les appels de classe / méthode entre les deux ne le sont pas. Même chose pour DIP.
Voir également:
la source
new Mockup<YourClass>() {}
et la classe entière, y compris ses constructeurs, est fausse, qu'il s'agisse d'une interface, d'une classe abstraite ou d'une classe concrète. Vous pouvez également "remplacer" le comportement du constructeur si vous le souhaitez. Je suppose qu'il existe des moyens équivalents à Mockito ou à Powermock.Il semble que les réponses des deux côtés de la clôture se résument comme suit:
Comme je l'ai indiqué dans ma réponse à la réponse de Yanni , je ne pense pas que l'on puisse jamais avoir une règle absolue sur les interfaces. La règle doit être, par définition, flexible. Ma règle sur les interfaces est qu'une interface doit être utilisée partout où vous créez une API. Et une API doit être créée partout où vous passez d'un domaine de responsabilité à un autre.
Pour un exemple (horriblement artificiel), disons que vous construisez une
Car
classe. Dans votre classe, vous aurez certainement besoin d'une couche d'interface utilisateur. Dans cet exemple particulier, il prend la forme d'unIginitionSwitch
,SteeringWheel
,GearShift
,GasPedal
etBrakePedal
. Depuis cette voiture contient unAutomaticTransmission
, vous n'avez pas besoin d'unClutchPedal
. (Et comme c'est une voiture épouvantable, il n'y a pas de climatisation, de radio ou de siège. En fait, les marchepieds manquent aussi - il vous suffit de vous accrocher à ce volant et d'espérer le meilleur!)Alors, laquelle de ces classes nécessite une interface? La réponse peut être la totalité ou aucune des réponses - en fonction de votre conception.
Vous pouvez avoir une interface qui ressemble à ceci:
À ce stade, le comportement de ces classes devient partie intégrante de l'interface / API ICabin. Dans cet exemple, les classes (le cas échéant) sont probablement simples, avec quelques propriétés et une ou deux fonctions. Et ce que vous dites implicitement dans votre conception, c'est que ces classes existent uniquement pour prendre en charge toute implémentation concrète d'ICabin, et qu'elles ne peuvent exister par elles-mêmes ou qu'elles n'ont pas de sens en dehors du contexte ICabin.
C'est la même raison pour laquelle vous ne testez pas les membres privés de test unitaire: ils n'existent que pour prendre en charge l'API publique. Par conséquent, leur comportement doit être testé en testant l'API.
Donc, si votre classe existe uniquement pour prendre en charge une autre classe et que, théoriquement, vous la considérez comme n'ayant pas vraiment son propre domaine, il est alors bon de sauter l'interface. Mais si votre classe est suffisamment importante pour que vous la considériez suffisamment grande pour avoir son propre domaine, allez-y et donnez-lui une interface.
MODIFIER:
Fréquemment (y compris dans cette réponse), vous allez lire des choses comme 'domaine', 'dépendance' (souvent associées à 'injection') qui ne vous disent rien du tout lorsque vous commencez à programmer (ce n'est certainement pas le cas signifie rien pour moi). Pour domain, cela signifie exactement ce que cela ressemble à:
En termes de mon exemple - considérons le
IgnitionSwitch
. Dans un wagon de voiture, le commutateur d'allumage est responsable de:Ces propriétés constituent le domaine du
IgnitionSwitch
, ou en d’autres termes, ce qu’elle sait et dont elle est responsable.Le
IgnitionSwitch
n'est pas responsable duGasPedal
. Le commutateur d'allumage ignore complètement la pédale d'accélérateur. Ils fonctionnent tous les deux de manière totalement indépendante (une voiture n’aurait aucune valeur sans eux deux!).Comme je l'ai dit à l'origine, cela dépend de votre conception. Vous pouvez concevoir un
IgnitionSwitch
qui a deux valeurs: On (True
) et Off (False
). Vous pouvez également le concevoir pour authentifier la clé fournie et une foule d’autres actions. C’est la partie la plus difficile d’être un développeur, c’est de décider où tracer les lignes dans le sable - et honnêtement, c’est tout à fait relatif. Ces lignes dans le sable sont toutefois importantes: c'est là que se trouve votre API et donc vos interfaces.la source
Non (YAGNI) , sauf si vous envisagez d'écrire des tests pour d'autres classes utilisant cette interface, qui auraient intérêt à se moquer de l'interface.
la source
De MSDN :
Généralement, dans le cas d’une classe unique, il ne sera pas nécessaire de mettre en œuvre une interface, mais compte tenu de l’avenir de votre projet, il peut être utile de définir formellement le comportement nécessaire des classes.
la source
Pour répondre à la question: il y a plus que cela.
L'intention est l'un des aspects importants d'une interface.
Une interface est "un type abstrait qui ne contient aucune donnée, mais expose des comportements" - Interface (informatique) Donc, s'il s'agit d'un comportement, ou d'un ensemble de comportements, pris en charge par la classe, une interface est probablement le modèle correct. Si, toutefois, le (s) comportement (s) est (sont) intrinsèque (s) au concept incarné par la classe, vous ne souhaiterez probablement pas du tout une interface.
La première question à poser est quelle est la nature de la chose ou du processus que vous essayez de représenter. Viennent ensuite les raisons pratiques pour appliquer cette nature d’une manière donnée.
la source
Depuis que vous avez posé cette question, je suppose que vous avez déjà compris l’intérêt de disposer d’une interface masquant plusieurs implémentations. Cela peut se manifester par le principe d'inversion de dépendance.
Cependant, la nécessité d'avoir une interface ou non ne dépend pas du nombre de ses implémentations. Le véritable rôle d’une interface est de définir un contrat précisant quel service doit être fourni et non pas comment il doit être mis en œuvre.
Une fois le contrat défini, deux équipes ou plus peuvent travailler indépendamment. Supposons que vous travaillez sur un module A et que cela dépend du module B, le fait de créer une interface sur B permet de continuer votre travail sans vous soucier de la mise en œuvre de B car tous les détails sont masqués par l'interface. Ainsi, la programmation distribuée devient possible.
Même si le module B n’a qu’une seule implémentation de son interface, celle-ci est toujours nécessaire.
En conclusion, une interface masque les détails de la mise en œuvre à ses utilisateurs. La programmation pour l’interface permet d’écrire plus de documents car il faut définir un contrat, écrire un logiciel plus modulaire, promouvoir les tests unitaires et accélérer la vitesse de développement.
la source
Toutes les réponses ici sont très bonnes. En effet, la plupart du temps, vous n'avez pas besoin de mettre en œuvre une interface différente. Mais il y a des cas où vous voudrez peut -être le faire quand même. Voici quelques cas où je le fais:
La classe implémente une autre interface que je ne souhaite pas exposer Il
arrive souvent avec une classe d'adaptateur qui ponte le code tiers.
La classe utilise une technologie spécifique qui ne devrait pas fuir,
principalement lorsqu’elle interagit avec des bibliothèques externes. Même s'il n'y a qu'une seule implémentation, j'utilise une interface pour ne pas introduire de couplage inutile avec la bibliothèque externe.
Communication entre couches
Même si une seule classe d'interface utilisateur implémente une classe de domaine, elle permet une meilleure séparation entre ces couches et évite surtout une dépendance cyclique.
Seulement un sous-ensemble de la méthode devrait être disponible pour la plupart des objets.
Surtout lorsque j'ai une méthode de configuration sur la classe concrète.
Donc au final, j'utilise une interface pour la même raison que j'utilise un champ privé: un autre objet ne devrait pas avoir accès à des choses auxquelles il ne devrait pas avoir accès. Si j'ai un cas comme ça, j'introduis une interface même si une seule classe l'implémente.
la source
Les interfaces sont vraiment importantes, mais essayez de contrôler leur nombre.
Après avoir créé des interfaces pour à peu près tout, il est facile de se retrouver avec du code «spaghetti haché». Je m'en remets à la plus grande sagesse d'Ayende Rahien qui a publié des propos très sages sur le sujet:
http://ayende.com/blog/153889/limit-your-abstractions-analyzing-a-ddd-application
Ceci est son premier post d'une série complète alors continuez à lire!
la source
Une des raisons pour lesquelles vous pouvez toujours souhaiter introduire une interface dans ce cas est de suivre le principe d'inversion de dépendance . En d’autres termes, le module qui utilise la classe dépendra de son abstraction (c’est-à-dire de l’interface) au lieu de dépendre d’une implémentation concrète. Il sépare les composants de haut niveau des composants de bas niveau.
la source
Il n'y a pas de vraie raison de faire quoi que ce soit. Les interfaces sont destinées à vous aider et non au programme de sortie. Ainsi, même si l'interface est implémentée par un million de classes, aucune règle ne vous oblige à en créer une. Vous en créez un pour que, lorsque vous-même ou quiconque utilise votre code, vous souhaitez modifier quelque chose, il se répercute sur toutes les implémentations. La création d'une interface vous aidera dans tous les cas futurs où vous voudrez peut-être créer une autre classe qui l'implémentera.
la source
Il n'est pas toujours nécessaire de définir une interface pour une classe.
Les objets simples tels que les objets de valeur n'ont pas plusieurs implémentations. Ils n'ont pas besoin d'être moqués non plus. L'implémentation peut être testée seule et, lorsque d'autres classes dépendantes d'entre elles sont testées, l'objet de valeur réelle peut être utilisé.
N'oubliez pas que la création d'une interface a un coût. Il doit être mis à jour tout au long de la mise en œuvre, il nécessite un fichier supplémentaire et certains IDE auront des difficultés à zoomer sur la mise en œuvre, pas sur l'interface.
Je définirais donc les interfaces uniquement pour les classes de niveau supérieur, pour lesquelles vous souhaitez une abstraction de la mise en œuvre.
Notez qu'avec une classe, vous obtenez une interface gratuitement. Outre l'implémentation, une classe définit une interface à partir de l'ensemble des méthodes publiques. Cette interface est implémentée par toutes les classes dérivées. Ce n'est pas à proprement parler une interface, mais il peut être utilisé exactement de la même manière. Donc, je ne pense pas qu'il soit nécessaire de recréer une interface qui existe déjà sous le nom de la classe.
la source