Je soupçonne que j'ai fait une erreur d'écolier ici, et je cherche des éclaircissements. Beaucoup de classes dans ma solution (C #) - j'ose dire la majorité - j'ai fini par écrire une interface correspondante pour. Par exemple, une interface "ICalculator" et une classe "Calculator" qui l'implémente, même si je ne remplacerai probablement jamais cette calculatrice par une implémentation différente. De plus, la plupart de ces classes résident dans le même projet que leurs dépendances - elles n'ont vraiment besoin d'être internal
, mais ont fini par être public
un effet secondaire de la mise en œuvre de leurs interfaces respectives.
Je pense que cette pratique de créer des interfaces pour tout découle de quelques mensonges: -
1) Je pensais à l'origine qu'une interface était nécessaire pour créer des simulations de test unitaire (j'utilise Moq), mais j'ai depuis découvert qu'une classe peut être moquée si ses membres le sont virtual
, et elle a un constructeur sans paramètre (corrigez-moi si J'ai tort).
2) Je pensais à l'origine qu'une interface était nécessaire pour enregistrer une classe avec le framework IoC (Castle Windsor), par exemple
Container.Register(Component.For<ICalculator>().ImplementedBy<Calculator>()...
alors qu'en fait je pouvais simplement enregistrer le type concret contre lui-même:
Container.Register(Component.For<Calculator>().ImplementedBy<Calculator>()...
3) L'utilisation d'interfaces, par exemple les paramètres du constructeur pour l'injection de dépendances, entraîne un "couplage lâche".
Alors suis-je devenu fou avec les interfaces?! Je connais les scénarios où vous utiliseriez "normalement" une interface, par exemple en exposant une API publique, ou pour des choses comme la fonctionnalité "enfichable". Ma solution a un petit nombre de classes qui correspondent à de tels cas d'utilisation, mais je me demande si toutes les autres interfaces ne sont pas nécessaires et devraient être supprimées? En ce qui concerne le point 3) ci-dessus, ne violerai-je pas le "couplage lâche" si je le faisais?
Edit : - Je suis juste en train de jouer avec Moq, et il semble que les méthodes soient publiques et virtuelles, et aient un constructeur public sans paramètre, pour pouvoir se moquer d'eux. Il semble donc que je ne puisse pas avoir de classes internes alors?
la source
Réponses:
En général, si vous créez une interface qui n'a qu'un seul implémenteur, vous écrivez simplement deux fois et perdez votre temps. Les interfaces à elles seules ne fournissent pas de couplage lâche si elles sont étroitement couplées à une seule implémentation ... code.
Je considérerais cela comme une odeur de code si presque toutes vos classes ont des interfaces. Cela signifie qu'ils travaillent presque tous ensemble d'une manière ou d'une autre. Je soupçonnerais que les classes en font trop (car il n'y a pas de classes auxiliaires) ou que vous en avez trop abstrait (oh, je veux une interface de fournisseur pour la gravité car cela pourrait changer!).
la source
1) Même si les classes concrètes sont moquables, cela nécessite toujours de créer des membres
virtual
et de fournir un constructeur sans paramètre, qui peut être envahissant et indésirable. Vous (ou les nouveaux membres de votre équipe) allez bientôt vous retrouver à ajouter systématiquementvirtual
des constructeurs sans paramètres dans chaque nouvelle classe sans arrière-pensée, simplement parce que "c'est comme ça que ça marche".Une interface est IMO une bien meilleure idée lorsque vous devez vous moquer d'une dépendance, car elle vous permet de différer la mise en œuvre jusqu'à ce que vous en ayez vraiment besoin, et permet un contrat clair et clair de la dépendance.
3) Comment est-ce un mensonge?
Je pense que les interfaces sont idéales pour définir des protocoles de messagerie entre des objets collaborateurs. Il peut y avoir des cas où leur besoin est discutable, comme avec les entités de domaine par exemple. Cependant, partout où vous avez un partenaire stable (c'est-à-dire une dépendance injectée par opposition à une référence transitoire) avec laquelle vous devez communiquer, vous devriez généralement envisager d'utiliser une interface.
la source
L'idée de l'IoC est de rendre les types de béton remplaçables. Donc, même si (pour le moment) vous n'avez qu'une seule calculatrice qui implémente ICalculator, une deuxième implémentation pourrait dépendre uniquement de l'interface et non des détails d'implémentation de Calculator.
Par conséquent, la première version de votre enregistrement IoC-Container est la bonne et celle que vous devriez utiliser à l'avenir.
Si vous rendez vos classes publiques ou internes, ce n'est pas vraiment lié, et le moq-ing non plus.
la source
Je serais vraiment plus inquiet du fait que vous semblez ressentir le besoin de "se moquer" de presque tous les types de votre projet.
Les doublons de test sont principalement utiles pour isoler votre code du monde extérieur (bibliothèques tierces, E / S disque, E / S réseau, etc.) ou de calculs très coûteux. Être trop libéral avec votre framework d'isolation (en avez-vous vraiment VRAIMENT besoin? Votre projet est-il si complexe?) Peut facilement conduire à des tests couplés à l'implémentation, et cela rend votre conception plus rigide. Si vous écrivez un tas de tests qui vérifient qu'une classe a appelé la méthode X et Y dans cet ordre, puis que vous avez passé les paramètres a, b et c à la méthode Z, obtenez-vous VRAIMENT de la valeur? Parfois, vous obtenez des tests comme celui-ci lorsque vous testez la journalisation ou l'interaction avec des bibliothèques tierces, mais cela devrait être l'exception, pas la règle.
Dès que votre framework d'isolement vous dit que vous devez rendre les choses publiques et que vous devez toujours avoir des constructeurs sans paramètres, il est temps de vous débarrasser de l'esquive, car à ce stade, votre framework vous force à écrire du mauvais (pire) code.
En parlant plus spécifiquement des interfaces, je trouve qu'elles sont utiles dans quelques cas clés:
Lorsque vous devez répartir des appels de méthode vers différents types, par exemple lorsque vous avez plusieurs implémentations (c'est la plus évidente, car la définition d'une interface dans la plupart des langages n'est qu'une collection de méthodes virtuelles)
Lorsque vous devez pouvoir isoler le reste de votre code d'une classe. Il s'agit de la manière normale de soustraire le système de fichiers et l'accès au réseau et la base de données et ainsi de suite de la logique de base de votre application.
Lorsque vous avez besoin d'une inversion de contrôle pour protéger les limites des applications. C'est l'un des moyens les plus puissants pour gérer les dépendances. Nous savons tous que les modules de haut niveau et les modules de bas niveau ne devraient pas se connaître, car ils changeront pour différentes raisons et vous NE VOULEZ PAS avoir de dépendances sur HttpContext dans votre modèle de domaine ou sur un cadre de rendu Pdf dans votre bibliothèque de multiplication matricielle . Faire en sorte que vos classes de limites implémentent des interfaces (même si elles ne sont jamais remplacées) aide à desserrer le couplage entre les couches et réduit considérablement le risque de dépendances s'infiltrant à travers les couches des types connaissant trop d'autres types.
la source
Je penche pour l'idée que, si une logique est privée, la mise en œuvre de cette logique ne devrait préoccuper personne, et en tant que telle ne devrait pas être testée. Si une logique est suffisamment complexe pour que vous vouliez la tester, cette logique a probablement un rôle distinct et devrait donc être publique. Par exemple, si j'extrais du code dans une méthode d'assistance privée, je trouve que c'est généralement quelque chose qui pourrait tout aussi bien se trouver dans une classe publique distincte (un formateur, un analyseur, un mappeur, peu importe).
En ce qui concerne les interfaces, je laisse le besoin d'avoir à écraser ou à me moquer de la classe me conduire. Des trucs comme des repos, je veux généralement pouvoir piquer ou me moquer. Un mappeur dans un test d'intégration, (généralement) pas tellement.
la source