Je suis en train de peser les avantages et les inconvénients entre DI et SL. Cependant, je me suis retrouvé dans la capture suivante 22, ce qui implique que je devrais simplement utiliser SL pour tout, et n'injecter qu'un conteneur IoC dans chaque classe.
DI Catch 22:
Certaines dépendances, comme Log4Net, ne conviennent tout simplement pas à DI. J'appelle ces méta-dépendances et je pense qu'elles devraient être opaques pour appeler du code. Ma justification étant que si une simple classe 'D' a été implémentée à l'origine sans journalisation, et qu'elle nécessite ensuite une journalisation, les classes dépendantes 'A', 'B' et 'C' doivent maintenant obtenir cette dépendance et la transmettre 'A' à 'D' (en supposant que 'A' compose 'B', 'B' compose 'C', etc.). Nous avons maintenant apporté des modifications de code importantes simplement parce que nous avons besoin de connecter une classe.
Nous avons donc besoin d’un mécanisme opaque pour obtenir des méta-dépendances. Deux me viennent à l’esprit: Singleton et SL. Le premier a des limites connues, principalement en ce qui concerne les capacités de portée rigide: au mieux, un Singleton utilisera une fabrique abstraite stockée au niveau de l'application (c'est-à-dire dans une variable statique). Cela permet une certaine flexibilité, mais n'est pas parfait.
Une meilleure solution consisterait à injecter un conteneur IoC dans de telles classes, puis à utiliser SL à partir de cette classe pour résoudre ces méta-dépendances à partir du conteneur.
D'où la capture 22: comme la classe est en train d'injecter un conteneur IoC, pourquoi ne pas l'utiliser pour résoudre toutes les autres dépendances?
J'apprécierais grandement vos pensées :)
Comme la classe reçoit maintenant un conteneur IoC, pourquoi ne pas l'utiliser pour résoudre également toutes les autres dépendances?
L'utilisation du modèle de localisation de service annule complètement l'un des principaux points d'injection de dépendance. Le point d’injection de dépendance est de rendre les dépendances explicites. Une fois que vous avez masqué ces dépendances en ne les rendant pas explicites dans un constructeur, vous ne faites plus d'injection de dépendances à part entière.
Ce sont tous des constructeurs pour une classe nommée Foo
(définie sur le thème de la chanson de Johnny Cash):
Faux:
public Foo() {
this.bar = new Bar();
}
Faux:
public Foo() {
this.bar = ServiceLocator.Resolve<Bar>();
}
Faux:
public Foo(ServiceLocator locator) {
this.bar = locator.Resolve<Bar>();
}
Droite:
public Foo(Bar bar) {
this.bar = bar;
}
Seul ce dernier rend la dépendance sur Bar
explicite.
En ce qui concerne la journalisation, il existe un bon moyen de le faire sans que cela ne se répande dans votre code de domaine (cela ne devrait pas être le cas, mais si c'est le cas, vous utilisez une période d'injection de dépendance). Étonnamment, les conteneurs IoC peuvent aider à résoudre ce problème. Commencez ici .
Service Locator est un anti-modèle, pour des raisons parfaitement décrites à http://blog.ploeh.dk/2010/02/03/ServiceLocatorIsAnAnPattern.aspx . En termes de journalisation, vous pouvez soit traiter cela comme une dépendance comme une autre et injecter une abstraction via le constructeur ou l'injection de propriété.
La seule différence avec log4net, c'est que cela nécessite le type de l'appelant qui utilise le service. En utilisant Ninject (ou un autre conteneur) Comment puis-je savoir le type qui demande le service? décrit comment vous pouvez résoudre ce problème (il utilise Ninject, mais s’applique à n’importe quel conteneur IoC).
Sinon, vous pouvez considérer la journalisation comme une préoccupation transversale, qu'il n'est pas approprié de mélanger avec le code de votre logique métier. Dans ce cas, vous pouvez utiliser l'interception fournie par de nombreux conteneurs IoC. http://msdn.Microsoft.com/en-us/library/ff647107.aspx décrit l'utilisation de l'interception avec Unity.
Mon opinion est que ça dépend. Parfois on est meilleur et parfois un autre. Mais je dirais que généralement je préfère DI. Il y a peu de raisons pour cela.
Lorsque la dépendance est injectée dans un composant, elle peut être traitée comme une partie de son interface. Il est donc plus facile pour l'utilisateur du composant de fournir ces dépendances, car elles sont visibles. Dans le cas de SL injecté ou de SL statique, les dépendances sont masquées et l'utilisation du composant est un peu plus difficile.
Les dépendances injectées sont meilleures pour les tests unitaires car vous pouvez simplement les imiter. Dans le cas de SL, vous devez configurer à nouveau les dépendances Locator + Mock. Donc, c'est plus de travail.
Parfois, la journalisation peut être implémentée à l'aide de AOP, afin d'éviter toute confusion avec la logique métier.
Sinon, les options sont:
Bien entendu, ce contexte doit être configurable pour que vous puissiez utiliser le raccourci/modèle pour les tests unitaires . Une autre utilisation suggérée d’AmbientContext est de placer le fournisseur Date/Heure actuel ici, afin que vous puissiez le tronquer pendant le test unitaire et accélère le temps si vous voulez.
Nous avons trouvé un compromis: utiliser DI, mais regrouper les dépendances de niveau supérieur dans un objet en évitant de refactoriser l'enfer si ces dépendances devaient changer.
Dans l'exemple ci-dessous, nous pouvons ajouter à 'ServiceDependencies' sans avoir à refactoriser toutes les dépendances dérivées.
Exemple:
public ServiceDependencies{
public ILogger Logger{get; private set;}
public ServiceDependencies(ILogger logger){
this.Logger = logger;
}
}
public abstract class BaseService{
public ILogger Logger{get; private set;}
public BaseService(ServiceDependencies dependencies){
this.Logger = dependencies.Logger; //don't expose 'dependencies'
}
}
public class DerivedService(ServiceDependencies dependencies,
ISomeOtherDependencyOnlyUsedByThisService additionalDependency)
: base(dependencies){
//set local dependencies here.
}
J’ai utilisé le framework Google Guice DI en Java, et j’ai découvert qu’il faisait bien plus que faciliter les tests. Par exemple, j'avais besoin d'un journal distinct par application (pas de classe), avec l'obligation supplémentaire que tout mon code de bibliothèque commune utilise le consignateur dans le contexte de l'appel en cours. L'injection de l'enregistreur a rendu cela possible. Certes, tout le code de la bibliothèque devait être modifié: le logger a été injecté dans les constructeurs. Au début, j'ai résisté à cette approche à cause de tous les changements de codage requis; j'ai finalement réalisé que les changements apportaient de nombreux avantages:
Inutile de dire que je suis maintenant un grand fan de DI et que je l'utilise pour toutes les applications, sauf les plus triviales.
Si l'exemple prend uniquement log4net comme dépendance, vous devez uniquement procéder comme suit:
ILog log = LogManager.GetLogger(typeof(Foo));
Il est inutile d'injecter la dépendance car log4net fournit une journalisation granulaire en prenant le type (ou une chaîne) en tant que paramètre.
En outre, DI n'est pas en corrélation avec SL. IMHO le but de ServiceLocator est de résoudre les dépendances facultatives.
Exemple: si le SL fournit une interface ILog, je vais écrire un daa de journalisation.
Je sais que cette question est un peu vieille, je pensais juste que je donnerais ma contribution.
En réalité, 9 fois sur 10, vous n'avez pas vraiment besoin SL et devriez vous fier à DI. Cependant, dans certains cas, vous devez utiliser SL. Un domaine que je me trouve à utiliser SL (ou une variante de celui-ci) est le développement de jeux.
À mon avis, un autre avantage de SL est la possibilité de faire circuler des classes internal
.
En voici un exemple:
internal sealed class SomeClass : ISomeClass
{
internal SomeClass()
{
// Add the service to the locator
ServiceLocator.Instance.AddService<ISomeClass>(this);
}
// Maybe remove of service within finalizer or dispose method if needed.
internal void SomeMethod()
{
Console.WriteLine("The user of my library doesn't know I'm doing this, let's keep it a secret");
}
}
public sealed class SomeOtherClass
{
private ISomeClass someClass;
public SomeOtherClass()
{
// Get the service and call a method
someClass = ServiceLocator.Instance.GetService<ISomeClass>();
someClass.SomeMethod();
}
}
Comme vous pouvez le constater, l’utilisateur de la bibliothèque n’a aucune idée de l’appel de cette méthode, car nous n’avons pas procédé à une ID, ce n’est pas possible.
Ceci concerne le "Service Locator est un anti-modèle" de Mark Seeman. Je peux me tromper ici. Mais je pensais juste que je devrais partager mes pensées aussi.
public class OrderProcessor : IOrderProcessor
{
public void Process(Order order)
{
var validator = Locator.Resolve<IOrderValidator>();
if (validator.Validate(order))
{
var shipper = Locator.Resolve<IOrderShipper>();
shipper.Ship(order);
}
}
}
La méthode Process () pour OrderProcessor ne suit pas le principe d'inversion de contrôle. Cela rompt également le principe de responsabilité unique au niveau de la méthode. Pourquoi une méthode devrait-elle s’intéresser à l’instanciation du
objets (via new ou n'importe quelle classe S.L.), il doit tout accomplir.
Au lieu que la méthode Process () crée les objets, le constructeur peut en réalité avoir les paramètres pour les objets respectifs (lire les dépendances) comme indiqué ci-dessous. Alors, comment un localisateur de service peut-il être différent d'un IOC
récipient. ET cela facilitera également les tests unitaires.
public class OrderProcessor : IOrderProcessor
{
public OrderProcessor(IOrderValidator validator, IOrderShipper shipper)
{
this.validator = validator;
this.shipper = shipper;
}
public void Process(Order order)
{
if (this.validator.Validate(order))
{
shipper.Ship(order);
}
}
}
//Caller
public static void main() //this can be a unit test code too.
{
var validator = Locator.Resolve<IOrderValidator>(); // similar to a IOC container
var shipper = Locator.Resolve<IOrderShipper>();
var orderProcessor = new OrderProcessor(validator, shipper);
orderProcessor.Process(order);
}
Je sais que les gens disent vraiment que la DI est le seul bon modèle IOC, mais je ne comprends pas. Je vais essayer de vendre un peu de SL. Je vais utiliser le nouveau framework MVC Core pour vous montrer ce que je veux dire. Les premiers moteurs DI sont vraiment complexes. Ce que les gens veulent vraiment dire quand ils disent DI, est d’utiliser un framework comme Unity, Ninject, Autofac ... qui fait tout le travail lourd pour vous, où SL peut être aussi simple que de créer une classe d’usine. Pour un petit projet rapide, c’est un moyen facile de faire IOC sans apprendre tout un cadre pour une bonne DI, ce n’est peut-être pas si difficile à apprendre, mais quand même . Passons au problème que la DI peut devenir. Je vais utiliser une citation tirée de la documentation MVC Core . "ASP.NET Core est conçu dès le départ pour prendre en charge et tirer parti de l'injection de dépendances." La plupart des gens disent que, à propos de DI "99% de votre code base ne devrait pas connaître votre conteneur IoC." Alors, pourquoi auraient-ils besoin de concevoir à partir de zéro si seulement 1% du code devait en être conscient, l'ancien MVC ne prenait-il pas en charge la DI? Eh bien, c’est le gros problème de DI, cela dépend de DI. Faire en sorte que tout fonctionne "comme il se doit" nécessite beaucoup de travail. Si vous regardez la nouvelle Action Injection, cela ne dépend-il pas de l'ID si vous utilisez l'attribut [FromServices]
. Maintenant, les gens de DI diront NON, vous êtes supposé aller avec des usines, pas avec ce genre de choses, mais comme vous pouvez le constater, même les gens qui fabriquent MVC l'ont bien fait. Le problème de DI est visible dans les filtres et vous indique également ce que vous devez faire pour obtenir une DI dans un filtre.
public class SampleActionFilterAttribute : TypeFilterAttribute
{
public SampleActionFilterAttribute():base(typeof(SampleActionFilterImpl))
{
}
private class SampleActionFilterImpl : IActionFilter
{
private readonly ILogger _logger;
public SampleActionFilterImpl(ILoggerFactory loggerFactory)
{
_logger = loggerFactory.CreateLogger<SampleActionFilterAttribute>();
}
public void OnActionExecuting(ActionExecutingContext context)
{
_logger.LogInformation("Business action starting...");
// perform some business logic work
}
public void OnActionExecuted(ActionExecutedContext context)
{
// perform some business logic work
_logger.LogInformation("Business action completed.");
}
}
}
Si vous utilisiez SL, vous auriez pu le faire avec var _logger = Locator.Get () ;. Et puis nous arrivons aux vues. Avec toute la bonne volonté concernant DI, ils devaient utiliser SL pour les vues. la nouvelle syntaxe @inject StatisticsService StatsService
est identique à var StatsService = Locator.Get<StatisticsService>();
. La partie la plus annoncée de DI est le test unitaire. Mais ce que les gens font, c’est tester des services fictifs sans aucun but ou avoir à brancher le moteur Dl pour effectuer de vrais tests. Et je sais que vous pouvez faire quelque chose de mal, mais les gens finissent par créer un localisateur SL même s’ils ne savent pas de quoi il s’agit. Là où peu de gens font de l'ID sans jamais l'avoir lu au préalable ..__ Mon plus gros problème avec l'ID est que l'utilisateur de la classe doit être conscient du fonctionnement interne de la classe pour pouvoir l'utiliser.
SL peut être utilisé de la bonne manière et présente certains avantages, notamment sa simplicité.