J'utilise Unity en C # pour l'injection de dépendance, mais la question devrait être applicable à tout langage et framework qui utilise l'injection de dépendance.
J'essaie de suivre le principe SOLID et j'ai donc eu beaucoup d'abstractions. Mais maintenant, je me demande s'il existe une meilleure pratique pour combien d'injections une classe devrait injecter?
Par exemple, j'ai un référentiel avec 9 injections. Sera-ce difficile à lire pour un autre développeur?
Les injections ont les responsabilités suivantes:
- IDbContextFactory - création du contexte pour la base de données.
- IMapper - Mappage des entités aux modèles de domaine.
- IClock - Abstracts DateTime.Now pour aider avec les tests unitaires.
- IPerformanceFactory - mesure le temps d'exécution pour des méthodes spécifiques.
- ILog - Log4net pour la journalisation.
- ICollectionWrapperFactory - Crée des collections (qui étend IEnumerable).
- IQueryFilterFactory - Génère des requêtes basées sur l'entrée qui interrogera db.
- IIdentityHelper - Récupère l'utilisateur connecté.
- IFaultFactory - Créer différentes FaultExceptions (j'utilise WCF).
Je ne suis pas vraiment déçu de la façon dont j'ai délégué les responsabilités, mais je commence à m'inquiéter pour la lisibilité.
Alors, mes questions:
Y a-t-il une limite au nombre d'injections qu'une classe devrait avoir? Et si oui, comment l'éviter?
De nombreuses injections limitent-elles la lisibilité ou l’améliorent-elles réellement?
la source
Réponses:
Trop de dépendances peuvent indiquer que la classe elle-même en fait trop. Afin de déterminer si cela ne fait pas trop:
Regardez la classe elle-même. Serait-il sensé de le diviser en deux, trois, quatre? Cela a-t-il un sens dans son ensemble?
Regardez les types de dépendances. Lesquels sont spécifiques à un domaine et ceux qui sont «mondiaux»? Par exemple, je ne considérerais pas
ILog
au même niveau queIQueryFilterFactory
: le premier serait de toute façon disponible dans la plupart des classes commerciales s'ils utilisent la journalisation. D'un autre côté, si vous trouvez beaucoup de dépendances spécifiques au domaine, cela peut indiquer que la classe en fait trop.Regardez les dépendances qui peuvent être remplacées par des valeurs.
Cela pourrait facilement être remplacé en
DateTime.Now
étant transmis directement aux méthodes qui nécessitent de connaître l'heure actuelle.En regardant les dépendances réelles, je ne vois rien qui pourrait indiquer que de mauvaises choses se produisent:
IDbContextFactory - création du contexte pour la base de données.
OK, nous sommes probablement dans une couche métier où les classes interagissent avec la couche d'accès aux données. Semble bien.
IMapper - Mappage des entités aux modèles de domaine.
Il est difficile de dire quoi que ce soit sans l'image globale. Il se peut que l'architecture soit incorrecte et que le mappage soit effectué directement par la couche d'accès aux données, ou il se peut que l'architecture soit parfaitement correcte. Dans tous les cas, il est logique d'avoir cette dépendance ici.
Un autre choix serait de diviser la classe en deux: l'un traitant de la cartographie, l'autre de la logique métier actuelle. Cela créerait une couche de facto qui séparera davantage le BL du DAL. Si les mappages sont complexes, ce pourrait être une bonne idée. Dans la plupart des cas, cependant, cela ne ferait qu'ajouter une complexité inutile.
IClock - Abstracts DateTime.Now pour aider avec les tests unitaires.
Il n'est probablement pas très utile d'avoir une interface (et une classe) séparée juste pour obtenir l'heure actuelle. Je passerais simplement le
DateTime.Now
aux méthodes qui nécessitent l'heure actuelle.Une classe distincte peut avoir du sens s'il existe d'autres informations, comme les fuseaux horaires ou les plages de dates, etc.
IPerformanceFactory - mesure le temps d'exécution pour des méthodes spécifiques.
Voir le point suivant.
ILog - Log4net pour la journalisation.
Ces fonctionnalités transcendantes doivent appartenir au cadre et les bibliothèques réelles doivent être interchangeables et configurables au moment de l'exécution (par exemple via app.config dans .NET).
Malheureusement, ce n'est pas (encore) le cas, ce qui vous permet soit de choisir une bibliothèque et de vous y tenir, soit de créer une couche d'abstraction pour pouvoir échanger les bibliothèques plus tard si nécessaire. Si votre intention est précisément d'être indépendant du choix de la bibliothèque, allez-y. Si vous êtes sûr que vous continuerez à utiliser la bibliothèque pendant des années, n'ajoutez pas d'abstraction.
Si la bibliothèque est trop complexe à utiliser, un motif de façade est logique.
ICollectionWrapperFactory - Crée des collections (qui étend IEnumerable).
Je suppose que cela crée des structures de données très spécifiques qui sont utilisées par la logique du domaine. Cela ressemble à une classe utilitaire. Utilisez plutôt une classe par structure de données avec les constructeurs appropriés. Si la logique d'initialisation est légèrement compliquée à intégrer dans un constructeur, utilisez des méthodes d'usine statiques. Si la logique est encore plus complexe, utilisez un modèle d'usine ou un modèle de générateur.
IQueryFilterFactory - Génère des requêtes basées sur l'entrée qui interrogera db.
Pourquoi n'est-ce pas dans la couche d'accès aux données? Pourquoi y a-t-il un
Filter
dans le nom?IIdentityHelper - Récupère l'utilisateur connecté.
Je ne sais pas pourquoi y a-t-il un
Helper
suffixe. Dans tous les cas, les autres suffixes ne seront pas particulièrement explicites non plus (IIdentityManager
?)Quoi qu'il en soit, il est parfaitement logique d'avoir cette dépendance ici.
IFaultFactory - Créer différentes FaultExceptions (j'utilise WCF).
La logique est-elle si complexe qu'elle nécessite d'utiliser un modèle d'usine? Pourquoi l'injection de dépendance est-elle utilisée pour cela? Souhaitez-vous échanger la création d'exceptions entre le code de production et les tests? Pourquoi?
J'essaierais de refaçonner cela en simple
throw new FaultException(...)
. Si des informations globales doivent être ajoutées à toutes les exceptions avant de les propager au client, WCF dispose probablement d'un mécanisme permettant d'attraper une exception non gérée et de la modifier et de la renvoyer au client.Mesurer la qualité par des chiffres est généralement aussi mauvais que d'être payé par des lignes de code que vous écrivez par mois. Vous pouvez avoir un grand nombre de dépendances dans une classe bien conçue, car vous pouvez avoir une classe de merde utilisant peu de dépendances.
De nombreuses dépendances rendent la logique plus difficile à suivre. Si la logique est difficile à suivre, la classe en fait probablement trop et devrait être divisée.
la source
Ceci est un exemple classique de DI vous disant que votre classe devient probablement trop grande pour être une seule classe. Ceci est souvent interprété comme "wow, DI rend mes constructeurs plus grands que jupiter, cette technique est horrible", mais ce qu'elle vous dit en fait, c'est que "votre classe a un bateau plein de dépendances". Sachant cela, nous pouvons soit
Il existe d'innombrables façons de gérer les dépendances, et il est impossible de dire ce qui fonctionne le mieux dans votre cas sans connaître votre code et votre application.
Pour répondre à vos dernières questions:
Oui, la limite supérieure est "trop". Combien est «trop»? «Trop» est lorsque la cohésion de la classe devient «trop faible». Tout dépend. Normalement, si votre réaction à une classe est "wow, cette chose a beaucoup de dépendances", c'est trop.
Je pense que cette question est trompeuse. La réponse peut être oui ou non. Mais ce n'est pas la partie la plus intéressante. Le point d'injecter des dépendances est de les rendre VISIBLES. Il s'agit de créer une API qui ne ment pas. Il s'agit d'empêcher l'état mondial. Il s'agit de rendre le code testable. Il s'agit de réduire le couplage.
Les classes bien conçues avec des méthodes raisonnablement conçues et nommées sont plus lisibles que celles mal conçues. DI n'améliore pas vraiment ni ne nuit à la lisibilité en soi, il fait juste ressortir vos choix de conception, et s'ils sont mauvais, cela vous piquera les yeux. Cela ne signifie pas que DI a rendu votre code moins lisible, cela vous a juste montré que votre code était déjà un gâchis, vous veniez de le cacher.
la source
Selon Steve McConnel, auteur de l'omniprésent "Code Complete", la règle générale est que plus de 7 est une odeur de code qui nuit à la maintenabilité. Personnellement, je pense que le nombre est inférieur dans la plupart des cas, mais faire correctement DI entraînera de nombreuses dépendances à injecter lorsque vous êtes très proche de la racine de composition. Ceci est normal et attendu. C'est l'une des raisons pour lesquelles les conteneurs IoC sont utiles et valent la complexité qu'ils ajoutent à un projet.
Donc, si vous êtes très proche du point d'entrée de votre programme, c'est normal et acceptable. Si vous êtes plus profondément dans la logique du programme, c'est probablement une odeur qui devrait être traitée.
la source