J'ai de nombreuses classes de base qui nécessitent ISessionContext de la base de données, ILogManager pour le journal et IService utilisé pour communiquer avec d'autres services. Je veux utiliser l'injection de dépendances pour cette classe utilisée par toutes les classes principales.
J'ai deux implémentations possibles. La classe principale qui accepte IAmbientContext avec les trois classes ou injecte pour toutes les classes les trois classes.
public interface ISessionContext
{
...
}
public class MySessionContext: ISessionContext
{
...
}
public interface ILogManager
{
}
public class MyLogManager: ILogManager
{
...
}
public interface IService
{
...
}
public class MyService: IService
{
...
}
Première solution:
public class AmbientContext
{
private ISessionContext sessionContext;
private ILogManager logManager;
private IService service;
public AmbientContext(ISessionContext sessionContext, ILogManager logManager, IService service)
{
this.sessionContext = sessionContext;
this.logManager = logManager;
this.service = service;
}
}
public class MyCoreClass(AmbientContext ambientContext)
{
...
}
deuxième solution (sans contexte ambiant)
public MyCoreClass(ISessionContext sessionContext, ILogManager logManager, IService service)
{
...
}
Quelle est la meilleure solution dans ce cas?
c#
dependency-injection
user3401335
la source
la source
IService
utilisé pour communiquer avec un autre service?" SiIService
représente une vague dépendance vis-à-vis d'autres services, cela ressemble à un localisateur de services et ne devrait pas exister. Votre classe doit dépendre d'interfaces qui décrivent explicitement ce que leur consommateur en fera. Aucune classe n'a jamais besoin d'un service pour donner accès à un service. Une classe a besoin d'une dépendance qui fait quelque chose de spécifique dont la classe a besoin.Réponses:
"Best" est beaucoup trop subjectif ici. Comme cela est courant avec de telles décisions, il s'agit d'un compromis entre deux façons également valables de réaliser quelque chose.
Si vous créez un
AmbientContext
et qui injectent dans de nombreuses classes, vous êtes potentiellement fournir davantage d' informations à chacun d'eux que nécessaire (par exemple , la classeFoo
ne peut utiliserISessionContext
, mais qui est dit au sujetILogManager
etISession
aussi).Si vous passez chacun via un paramètre, vous ne dites à chaque classe que les choses dont elle a besoin. Mais, le nombre de paramètres peut rapidement augmenter et vous pouvez trouver que vous avez beaucoup trop de constructeurs et de méthodes avec de nombreux paramètres très répétés, qui pourraient être simplifiés via une classe de contexte.
Il s'agit donc d'équilibrer les deux et de choisir celui qui convient à votre situation. Si vous n'avez qu'une seule classe et trois paramètres, je ne m'embêterais pas personnellement
AmbientContext
. Pour moi, le point de basculement serait probablement de quatre paramètres. Mais c'est une pure opinion. Votre point de basculement sera probablement différent du mien, alors allez-y avec ce qui vous convient.la source
La terminologie de la question ne correspond pas vraiment à l'exemple de code. Le
Ambient Context
est un modèle utilisé pour récupérer une dépendance de n'importe quelle classe dans n'importe quel module aussi facilement que possible, sans polluer chaque classe pour accepter l'interface de la dépendance, mais en gardant toujours l'idée d'inversion de contrôle. De telles dépendances sont généralement dédiées à la journalisation, à la sécurité, à la gestion de session, aux transactions, à la mise en cache, à l'audit, ainsi à tout problème transversal dans cette application. Il est en quelque sorte gênant d'ajouter unILogging
,ISecurity
,ITimeProvider
aux constructeurs et la plupart du temps toutes les classes ont besoin tout en même temps, donc je comprends votre besoin.Et si la durée de vie de l'
ISession
instance est différente deILogger
celle? Peut-être que l'instance ISession devrait être créée à chaque demande et l'ILogger une fois. Donc, avoir toutes ces dépendances régies par un objet qui n'est pas le conteneur lui-même ne semble pas le bon choix en raison de tous ces problèmes avec la gestion et la localisation de la durée de vie et d'autres décrits dans ce fil.La
IAmbientContext
question ne résout pas le problème de ne pas polluer tous les constructeurs. Vous devez toujours l'utiliser dans la signature du constructeur, bien sûr, une seule fois cette fois.Donc, le moyen le plus simple n'est PAS d'utiliser l'injection de constructeur ou tout autre mécanisme d'injection pour gérer les dépendances transversales, mais en utilisant un appel statique . Nous voyons en fait ce modèle assez souvent, mis en œuvre par le cadre lui-même. Vérifiez Thread.CurrentPrincipal qui est une propriété statique qui renvoie une implémentation de l'
IPrincipal
interface. Il est également paramétrable pour que vous puissiez modifier l'implémentation si vous le souhaitez, donc vous n'y êtes pas associé.MyCore
ressemble maintenant à quelque chose commeCe modèle et les implémentations possibles ont été décrits en détail par Mark Seemann dans cet article . Il peut y avoir des implémentations qui dépendent du conteneur IoC lui-même que vous utilisez.
Vous souhaitez éviter
AmbientContext.Current.Logger
,AmbientContext.Current.Session
pour les mêmes raisons que celles décrites ci-dessus.Mais vous avez d'autres options pour résoudre ce problème: utilisez des décorateurs, l'interception dynamique si votre conteneur a cette capacité ou AOP. Le contexte ambiant devrait être le dernier recours car ses clients cachent leurs dépendances à travers lui. J'utiliserais toujours le contexte ambiant si l'interface imite vraiment mon impulsion à utiliser une dépendance statique comme
DateTime.Now
ouConfigurationManager.AppSettings
et ce besoin augmente assez souvent. Mais à la fin, l'injection de constructeur pourrait ne pas être une si mauvaise idée pour obtenir ces dépendances omniprésentes.la source
J'éviterais le
AmbientContext
.Premièrement, si la classe en dépend,
AmbientContext
vous ne savez pas vraiment ce qu'elle fait. Vous devez regarder son utilisation de cette dépendance pour déterminer laquelle de ses dépendances imbriquées il utilise. Vous ne pouvez pas non plus regarder le nombre de dépendances et dire si votre classe en fait trop, car l'une de ces dépendances peut en fait représenter plusieurs dépendances imbriquées.Deuxièmement, si vous l'utilisez pour éviter plusieurs dépendances de constructeur, cette approche encouragera d'autres développeurs (vous y compris) à ajouter de nouveaux membres à cette classe de contexte ambiant. Ensuite, le premier problème est aggravé.
Troisièmement, se moquer de la dépendance
AmbientContext
est plus difficile parce que vous devez déterminer dans chaque cas s'il faut se moquer de tous ses membres ou uniquement ceux dont vous avez besoin, puis configurer une maquette qui renvoie ces simulations (ou teste des doublons). votre unité teste plus difficile à écrire, à lire et à maintenir.Quatrièmement, il manque de cohésion et viole le principe de responsabilité unique. C'est pourquoi il a un nom comme "AmbientContext", car il fait beaucoup de choses sans rapport et il n'y a aucun moyen de le nommer en fonction de ce qu'il fait.
Et il viole probablement le principe de ségrégation d'interface en introduisant des membres d'interface dans des classes qui n'en ont pas besoin.
la source
Le second (sans le wrapper d'interface)
Sauf s'il existe une certaine interaction entre les différents services qui doit être encapsulée dans une classe intermédiaire, cela ne fait que compliquer votre code et limite la flexibilité lorsque vous introduisez l '«interface des interfaces»
la source