Pour être interchangeables et testables, normalement les services avec logique doivent avoir une interface, par ex.
public class FooService: IFooService
{ ... }
Côté design, je suis d'accord avec cela, mais l'une des choses qui me dérange avec cette approche est que pour un service, vous devrez déclarer deux choses (la classe et l'interface), et dans notre équipe, normalement deux fichiers (un pour la classe et un pour l'interface). Un autre inconfort est la difficulté de navigation car l'utilisation de "Aller à la définition" dans IDE (VS2010) pointera vers l'interface (puisque les autres classes font référence à l'interface), pas la classe réelle.
Je pensais que l'écriture d'IFooService dans le même fichier que FooService réduira l'étrangeté ci-dessus. Après tout, IFooService et FooService sont très liés. Est-ce une bonne pratique? Y a-t-il une bonne raison pour que IFooService se trouve dans son propre fichier?
Il ne doit pas nécessairement se trouver dans son propre fichier, mais votre équipe doit décider d'une norme et s'y tenir.
En outre, vous avez raison de dire que "Aller à la définition" vous amène à l'interface, mais si vous avez Resharper installé, il suffit d'un clic pour ouvrir une liste des classes/interfaces dérivées de cette interface, donc ce n'est pas grave. C'est pourquoi je garde l'interface dans un fichier séparé.
Je pense que vous devriez les conserver dans des fichiers séparés. Comme vous l'avez dit, l'idée est de rester testable et interchangeable. En plaçant l'interface dans le même fichier que votre implémentation, vous associez l'interface à l'implémentation spécifique. Si vous décidez de créer un faux objet ou une autre implémentation, l'interface ne sera pas logiquement séparée de FooService.
Selon SOLID, non seulement vous devez créer l'interface, et non seulement si elle doit être dans un fichier différent, elle doit être dans un assemblage différent.
Pourquoi? Parce que toute modification apportée à un fichier source qui se compile dans un assembly nécessite une recompilation de l'assembly, et toute modification apportée à un assembly nécessite la recompilation de tout assembly dépendant. Donc, si votre objectif, basé sur SOLID, est de pouvoir remplacer une implémentation A par une implémentation B, alors que la classe C dépendant de l'interface I n'a pas besoin de faire la différence, vous devez vous assurer que l'assembly avec I en elle ne change pas, protégeant ainsi les usages.
"Mais c'est juste une recompilation", je vous entends protester. C'est possible, mais dans votre application pour smartphone, ce qui est plus facile avec la bande passante de données de vos utilisateurs; télécharger un binaire qui a changé, ou télécharger ce binaire et cinq autres avec du code qui en dépend? Tous les programmes ne sont pas écrits pour être consommés par les ordinateurs de bureau sur un réseau local. Même dans ce cas, où la bande passante et la mémoire sont bon marché, les versions de correctifs plus petites peuvent avoir de la valeur car elles sont triviales pour être déployées sur l'ensemble du LAN via Active Directory ou des couches de gestion de domaine similaires; vos utilisateurs n'attendront que quelques secondes pour qu'il soit appliqué la prochaine fois qu'ils se connectent au lieu de quelques minutes pour que le tout soit réinstallé. Sans oublier que, moins il y a d'assemblages à recompiler lors de la construction d'un projet, plus il se construit rapidement, ce qui vous rend plus productif car vous passez moins de temps à attendre que 50 assemblys soient créés pour chaque modification que vous apportez.
Maintenant, l'avertissement: ce n'est pas toujours possible ou faisable. La façon la plus simple de le faire est de créer un projet "d'interfaces" centralisé. Cela a ses propres inconvénients; le code devient moins réutilisable car le projet d'interface ET le projet d'implémentation doivent être référencés dans d'autres applications réutilisant la couche de persistance ou d'autres composants clés de votre application. Vous pouvez surmonter ce problème en divisant les interfaces en assemblages plus étroitement couplés, mais vous avez alors plus de projets dans votre application, ce qui rend une construction complète très douloureuse. La clé est l'équilibre et le maintien de la conception à couplage lâche; vous pouvez généralement déplacer des fichiers si nécessaire, donc quand vous voyez qu'une classe aura besoin de nombreuses modifications, ou que de nouvelles implémentations d'une interface seront nécessaires régulièrement (peut-être pour interfacer avec les versions nouvellement prises en charge d'autres logiciels, ou types de fichiers, etc. ), vous pouvez le séparer de son interface et protéger les usages de la connaissance de ces modifications.
Pourquoi le fait d'avoir des fichiers séparés est un inconfort? Pour moi, c'est beaucoup plus propre et plus propre. Il est courant de créer un sous-dossier nommé "Interfaces" et d'y coller vos fichiers IFooServer.cs, si vous voulez voir moins de fichiers dans votre Explorateur de solutions.
La raison pour laquelle l'interface est définie dans son propre fichier est pour la même raison que les classes sont généralement définies dans leur propre fichier: la gestion de projet est plus simple lorsque votre structure logique et la structure de fichier sont identiques, de sorte que vous savez toujours quel fichier une classe donnée est définie dans. Cela peut vous faciliter la vie lors du débogage (les traces de pile d'exceptions vous donnent généralement des numéros de fichier et de ligne) ou lors de la fusion de code source dans un référentiel de contrôle de source.
Il est souvent recommandé de laisser vos fichiers de code ne contenir qu'une seule classe ou une seule interface. Mais ces pratiques de codage sont un moyen d'arriver à une fin - pour mieux structurer votre code et le rendre plus facile à utiliser. Si vous, et votre équipe, trouvez plus facile de travailler avec si les classes sont maintenues avec leurs interfaces, faites-le par tous les moyens.
Personnellement, je préfère avoir l'interface et la classe dans le même fichier lorsqu'il n'y a qu'une seule classe qui implémente l'interface, comme dans votre cas.
En ce qui concerne les problèmes que vous rencontrez avec la navigation, je peux fortement recommander ReSharper . Il contient des raccourcis très utiles pour passer directement à la méthode implémentant une méthode d'interface spécifique.
Il y a de nombreuses fois où vous voulez que votre interface soit non seulement dans un fichier séparé de la classe, mais même dans un Assemblage séparé tout à fait.
Par exemple, une interface de contrat de service WCF peut être partagée par le client et le service si vous avez le contrôle sur les deux extrémités du câble. En déplaçant l'interface dans son propre assembly, elle aura moins de dépendances d'assembly. Cela permet au client de consommer beaucoup plus facilement, en relâchant le couplage avec sa mise en œuvre.
Les interfaces appartiennent à leurs clients et non à leurs implémentations, telles que définies dans les principes, pratiques et modèles agiles de Robert C. Martin. Ainsi, combiner interface et implémentation au même endroit est contraire à son principe.
Le code client dépend de l'interface. Cela permet de compiler et de déployer le code client et l'interface sans l'implémentation. Que vous pouvez avoir différentes implémentations fonctionnant comme plugins au code client.
MISE À JOUR: Ce n'est pas un principe uniquement agile ici. Le Gang of Four in Design Patterns, en 1994, parle déjà de clients adhérant aux interfaces et programmant les interfaces. Leur point de vue est similaire.
Il est rarement, voire jamais, logique d'avoir une seule implémentation d'une interface1. Si vous mettez une interface publique et une classe publique implémentant cette interface dans le même fichier, il y a de fortes chances que vous n'ayez pas besoin d'une interface.
Lorsque la classe que vous colocalisez avec l'interface est abstract, et que vous savez que toutes les implémentations de votre interface doivent hériter de cette classe abstraite, localiser les deux dans le même fichier est logique. Vous devriez toujours examiner attentivement votre décision d'utiliser une interface: demandez-vous si une classe abstraite en elle-même conviendrait bien, et supprimez l'interface si la réponse est affirmative.
Cependant, en règle générale, vous devez vous en tenir à la stratégie "une classe/interface publique correspond à un fichier": elle est facile à suivre et facilite la navigation dans votre arbre source.
Mettre une classe et une interface dans le même fichier ne met aucune limite sur la façon dont l'un ou l'autre peut être utilisé. Une interface peut toujours être utilisée pour les simulations, etc. de la même manière, même si elle se trouve dans le même fichier qu'une classe qui l'implémente. La question pourrait être perçue uniquement comme une question de commodité organisationnelle, auquel cas je dirais faire tout ce qui vous facilite la vie!
Bien sûr, on pourrait faire valoir que le fait d'avoir les deux dans le même fichier pourrait conduire les imprudents à les considérer comme étant couplés plus programmatiques qu'ils ne le sont réellement, ce qui constitue un risque.
Personnellement, si je tombais sur une base de code qui utilisait une convention sur l'autre, je ne serais pas trop inquiet de toute façon.
Personnellement, je n'aime pas le "je" devant une interface. En tant qu'utilisateur d'un tel type, je ne veux pas voir qu'il s'agit d'une interface ou non. Le type est la chose intéressante. En termes de dépendances, votre FooService est UNE implémentation possible d'IFooService. L'interface doit être proche de l'endroit où elle est utilisée. D'un autre côté, la mise en œuvre doit être à un endroit où elle peut être facilement modifiée sans affecter le client. Donc, je préférerais deux fichiers - normalement.
Le codage vers les interfaces peut être mauvais, est en fait traité comme un anti-modèle pour certains contextes et n'est pas une loi. Habituellement, un bon exemple de codage des interfaces anti-modèle est lorsque vous avez une interface qui n'a qu'une seule implémentation dans votre application. Un autre serait si le fichier d'interface devient si gênant que vous devez cacher sa déclaration dans le fichier de la classe implémentée.