简体   繁体   English

依赖注入与服务位置

[英]Dependency Injection vs Service Location

I am currently weighing up the advantages and disadvantages between DI and SL. 我目前正在权衡DI和SL之间的优缺点。 However, I have found myself in the following catch 22 which implies that I should just use SL for everything, and only inject an IoC container into each class. 但是,我发现自己处于以下问题22中,这意味着我应该只使用SL作为一切,并且只在每个类中注入一个IoC容器。

DI Catch 22: DI Catch 22:

Some dependencies, like Log4Net, simply do not suit DI. 一些依赖项,如Log4Net,根本不适合DI。 I call these meta-dependencies and feel they should be opaque to calling code. 我称之为元依赖关系并认为它们对调用代码应该是不透明的。 My justification being that if a simple class 'D' was originally implemented without logging, and then grows to require logging, then dependent classes 'A', 'B', and 'C' must now somehow obtain this dependency and pass it down from 'A' to 'D' (assuming 'A' composes 'B', 'B' composes 'C', and so on). 我的理由是,如果一个简单的类'D'最初是在没有记录的情况下实现的,然后增长到需要记录,那么依赖类'A','B'和'C'现在必须以某种方式获得这种依赖并将其从'A'到'D'(假设'A'组成'B','B'组成'C',依此类推)。 We have now made significant code changes just because we require logging in one class. 我们现在已经进行了重要的代码更改,因为我们需要登录一个类。

We therefore require an opaque mechanism for obtaining meta-dependencies. 因此,我们需要一种不透明的机制来获取元依赖性。 Two come to mind: Singleton and SL. 我想到了两个:Singleton和SL。 The former has known limitations, primarily with regards to rigid scoping capabilities: at best a Singleton will use an Abstract Factory which is stored at application scope (ie. in a static variable). 前者具有已知的局限性,主要是关于刚性范围的能力:最好的是Singleton将使用存储在应用程序范围内的抽象工厂(即在静态变量中)。 This allows some flexibility, but is not perfect. 这允许一些灵活性,但并不完美。

A better solution would be to inject an IoC container into such classes, and then use SL from within that class to resolve these meta-dependencies from the container. 更好的解决方案是将IoC容器注入此类,然后使用该类中的SL从容器中解析这些元依赖关系。

Hence catch 22: because the class is now being injected with an IoC container, then why not use it to resolve all other dependencies too? 因此,捕获22:因为类现在正在注入IoC容器,那么为什么不使用它来解析所有其他依赖项呢?

I would greatly appreciate your thoughts :) 我非常感谢你的想法:)

Because the class is now being injected with an IoC container, then why not use it to resolve all other dependencies too? 因为该类现在正在注入IoC容器,那么为什么不使用它来解析所有其他依赖项呢?

Using the service locator pattern completely defeats one of the main points of dependency injection. 使用服务定位器模式完全打败了依赖注入的一个主要点。 The point of dependency injection is to make dependencies explicit. 依赖注入的关键是使依赖关系显式化。 Once you hide those dependencies by not making them explicit parameters in a constructor, you're no longer doing full-fledged dependency injection. 一旦你通过不在构造函数中使它们成为显式参数来隐藏这些依赖项,就不再需要完全成熟的依赖注入了。

These are all constructors for a class named Foo (set to the theme of the Johnny Cash song): 这些都是名为Foo的类的构造函数(设置为Johnny Cash歌曲的主题):

Wrong: 错误:

public Foo() {
    this.bar = new Bar();
}

Wrong: 错误:

public Foo() {
    this.bar = ServiceLocator.Resolve<Bar>();
}

Wrong: 错误:

public Foo(ServiceLocator locator) {
    this.bar = locator.Resolve<Bar>();
}

Right: 对:

public Foo(Bar bar) {
    this.bar = bar;
}

Only the latter makes the dependency on Bar explicit. 只有后者才能明确依赖Bar

As for logging, there's a right way to do it without it permeating into your domain code (it shouldn't but if it does then you use dependency injection period). 对于日志记录,有一种正确的方法可以实现它,而不会渗透到您的域代码中(它不应该,但如果确实如此,则使用依赖注入时段)。 Amazingly, IoC containers can help with this issue. 令人惊讶的是,IoC容器可以帮助解决这个问题。 Start here . 这里开始。

Service Locator is an anti-pattern, for reasons excellently described at http://blog.ploeh.dk/2010/02/03/ServiceLocatorIsAnAntiPattern.aspx . 服务定位器是一种反模式,其原因在http://blog.ploeh.dk/2010/02/03/ServiceLocatorIsAnAntiPattern.aspx中有详细描述。 In terms of logging, you could either treat that as a dependency just like any other, and inject an abstraction via constructor or property injection. 在日志记录方面,您可以像其他任何一样将其视为依赖项,并通过构造函数或属性注入注入抽象。

The only difference with log4net, is that it requires the type of the caller that uses the service. 与log4net唯一的区别是它需要使用该服务的调用者类型。 Using Ninject (or some other container) How can I find out the type that is requesting the service? 使用Ninject(或其他一些容器)如何找出请求服务的类型? describes how you can solve this (it uses Ninject, but is applicable to any IoC container). 描述了如何解决这个问题(它使用Ninject,但适用于任何IoC容器)。

Alternatively, you could think of logging as a cross cutting concern, which isn't appropriate to mix with your business logic code, in which case you can use interception which is provided by many IoC containers. 或者,您可以将日志记录视为交叉问题,这不适合与业务逻辑代码混合使用,在这种情况下,您可以使用由许多IoC容器提供的拦截。 http://msdn.microsoft.com/en-us/library/ff647107.aspx describes using interception with Unity. http://msdn.microsoft.com/en-us/library/ff647107.aspx描述了使用Unity进行拦截。

My opinion is that it depends. 我的意见是,这取决于。 Sometimes one is better and sometimes another. 有时一个更好,有时另一个。 But I'd say that generaly I prefer DI. 但我会说通常我更喜欢DI。 There are few reasons for that. 原因很少。

  1. When dependency is injected somehow into component it can be treated as part of its interface. 当以某种方式将依赖注入组件时,它可以被视为其接口的一部分。 Thus its easier for component's user to supply this dependecies, cause they are visible. 因此,组件的用户更容易提供这种依赖,因为它们是可见的。 In case of injected SL or Static SL that dependencies are hidden and usage of component is a bit harder. 在注入SL或静态SL的情况下,隐藏了依赖关系并且组件的使用有点困难。

  2. Injected dependecies are better for unit testing cause you can simply mock them. 注入的依赖对于单元测试更好,因为您可以简单地模拟它们。 In case of SL you have to setup Locator + mock dependencies again. 在SL的情况下,您必须再次设置Locator + mock依赖项。 So it is more work. 所以这是更多的工作。

Sometimes logging can be implemented using AOP , so that it doesn't mix with business logic. 有时可以使用AOP实现日志记录,因此它不会与业务逻辑混合。

Otherwise, options are : 否则,选项是:

  • use an optional dependency (such as setter property), and for unit test you don't inject any logger. 使用可选的依赖项 (例如setter属性),对于单元测试,不要注入任何记录器。 IOC container will takes care of setting it automatically for you if you run in production. 如果您在生产中运行,IOC容器将自动为您设置它。
  • When you have a dependency that almost every object of your app is using ("logger" object being the most commmon example), it's one of the few cases where the singleton anti-pattern becomes a good practice. 当你有一个依赖项,几乎你的应用程序的每个对象都在使用(“记录器”对象是最常见的例子),这是单身反模式成为一个好习惯的少数情况之一。 Some people call these "good singletons" an Ambient Context : http://aabs.wordpress.com/2007/12/31/the-ambient-context-design-pattern-in-net/ 有些人将这些“好单身人士”称为环境背景http//aabs.wordpress.com/2007/12/31/the-ambient-context-design-pattern-in-net/

Of course this context has to be configurable, so that you can use stub/mock for unit testing. 当然,这个上下文必须是可配置的,这样你就可以使用stub / mock进行单元测试。 Another suggested use of AmbientContext, is to put the current Date/Time provider there , so that you can stub it during unit test, and accelerates time if you want. 另一个建议使用AmbientContext的方法是将当前的日期/时间提供程序放在那里,以便您可以在单元测试期间将其存根,并根据需要加速时间。

I have used the Google Guice DI framework in Java, and discovered that it does much more than make testing easier. 我在Java中使用了Google Guice DI框架,并发现它不仅仅使测试变得更容易。 For example, I needed a separate log per application (not class), with the further requirement that all my common library code use the logger in the current call context. 例如,我需要为每个应用程序 (不是类)单独记录日志,并进一步要求所有公共库代码在当前调用上下文中使用记录器。 Injecting the logger made this possible. 注入记录器使这成为可能。 Admittedly, all the library code needed to be changed: the logger was injected in the constructors. 不可否认,所有库代码都需要更改:记录器是在构造函数中注入的。 At first, I resisted this approach because of all the coding changes required; 起初,由于所需的所有编码更改,我拒绝这种方法; eventually I realized that the changes had many benefits: 最终我意识到这些变化有很多好处:

  • The code became simpler 代码变得更简单了
  • The code became much more robust 代码变得更加健壮
  • The dependencies of a class became obvious 类的依赖性变得明显
  • If there were many dependencies, it was a clear indication that a class needed refactoring 如果存在许多依赖关系,则清楚地表明类需要重构
  • Static singletons were eliminated 静态单身人士被淘汰出局
  • The need for session or context objects disappeared 对会话或上下文对象的需求消失了
  • Multi-threading became much easier, because the DI container could be built to contain just one thread, thus eliminating inadvertent cross-contamination 多线程变得更容易,因为DI容器可以构建为仅包含一个螺纹,从而消除了无意的交叉污染

Needless to say, I am now a big fan of DI, and use it for all but the most trivial applications. 毋庸置疑,我现在是DI的忠实粉丝,除了最琐碎的应用程序之外,它还可以用于所有应用程序。

We've landed on a compromise: use DI but bundle top-level dependencies into an object avoiding refactoring hell should those dependencies change. 我们已经达成了妥协:使用DI但是将顶级依赖关系捆绑到一个对象中,避免在这些依赖关系发生变化时重构地狱。

In the example below, we can add to 'ServiceDependencies' without having to refactor all derived dependencies. 在下面的示例中,我们可以添加到“ServiceDependencies”,而无需重构所有派生的依赖项。

Example: 例:

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.
}

This is regarding the 'Service Locator is an Anti-Pattern' by Mark Seeman. 这是关于Mark Seeman的“服务定位器是一种反模式”。 I might be wrong here. 我可能在这里错了。 But I just thought I should share my thoughts too. 但我只是觉得我也应该分享我的想法。

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);
        }
    }
}

The Process() method for OrderProcessor does not actually follow the 'Inversion of Control' principle. OrderProcessor的Process()方法实际上并不遵循“控制反转”原则。 It also breaks the Single Responsibility principle at the method level. 它还打破了方法级别的单一责任原则。 Why should a method be concerned with instantiating the 为什么一个方法应该关注实例化

objects(via new or any SL class) it needs to accomplish anything. 对象(通过新的或任何SL类)它需要完成任何事情。

Instead of having the Process() method create the objects the constructor can actually have the parameters for the respective objects(read dependencies) as shown below. 而不是使用Process()方法创建对象,构造函数实际上可以具有相应对象的参数(读取依赖项),如下所示。 Then HOW can a Service Locator be any different from a IOC 那么服务定位器如何与IOC有任何不同

container. 容器。 AND it will aid in Unit Testing as well. 它也有助于单元测试。

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);

}

I know this question is a little old, I just thought I would give my input. 我知道这个问题有点老了,我以为我会给出我的意见。

In reality, 9 times out of 10 you really don't need SL and should rely on DI. 实际上,10次中有9次你真的不需要 SL并且应该依赖DI。 However, there are some cases where you should use SL. 但是,在某些情况下您应该使用SL。 One area that I find myself using SL (or a variation, thereof) is in game development. 我发现自己使用SL(或其变体)的一个领域是游戏开发。

Another advantage of SL (in my opinion) is the ability to pass around internal classes. SL(在我看来)的另一个优点是能够传递internal类。

Below is an example: 以下是一个例子:

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();
    }
}

As you can see, the user of the library has no idea this method was called, because we didn't DI, not that we'd be able to anyways. 正如你所看到的,库的用户不知道这个方法被调用了,因为我们没有DI,不是我们无论如何都不能。

If the example only takes log4net as dependency, then you only need to do this: 如果该示例仅将log4net作为依赖项,那么您只需要执行以下操作:

ILog log = LogManager.GetLogger(typeof(Foo));

There is no point to inject the dependency as log4net provides granular logging by taking the type (or a string) as parameter. 没有必要注入依赖关系,因为log4net通过将类型(或字符串)作为参数提供粒度日志记录。

Also, DI is not correlated with SL. 此外,DI与SL无关。 IMHO the purpose of ServiceLocator is for resolve optional dependencies. 恕我直言,ServiceLocator的目的是解决可选的依赖项。

Eg: If the SL provides an ILog interface, i will write logging daa. 例如:如果SL提供ILog接口,我将编写日志记录daa。

I know that people are really saying DI is the only good IOC pattern but I don't get this. 我知道人们真的说DI是唯一的好IOC模式,但我不明白。 I will try to sell SL a bit. 我会尝试卖掉SL。 I will use the new MVC Core framework to show you what I mean. 我将使用新的MVC Core框架向您展示我的意思。 First DI engines are really complex. 第一台DI发动机非常复杂。 What people really mean when they say DI, is use some framework like Unity, Ninject, Autofac... that do all the heavy lifting for you, where SL can be as simple as making a factory class. 当人们说DI时,人们真正的意思是使用像Unity,Ninject,Autofac这样的框架......为你做所有繁重的工作,SL可以像制造工厂一样简单。 For a small fast project this is an easy way to do IOC without learning a whole framework for proper DI, they might not be that difficult to learn but still. 对于一个小型的快速项目,这是一个简单的方法来做IOC而不学习正确的DI的整个框架,他们可能不是那么难学,但仍然。 Now to the problem that DI can become. 现在到了DI可以成为的问题。 I will use a quote from MVC Core docs. 我将使用MVC Core docs的引用。 "ASP.NET Core is designed from the ground up to support and leverage dependency injection." “ASP.NET Core是从头开始设计的,用于支持和利用依赖注入。” Most people say that about DI "99% of your code base should have no knowledge of your IoC container." 大多数人都说DI“99%的代码库应该不了解你的IoC容器。” So why would they need to design from ground up if only 1% of code should be aware of it, didn't old MVC support DI? 那么为什么他们需要从头开始设计,如果只有1%的代码应该知道它,那么旧的MVC不支持DI吗? Well this is the big problem of DI it depends on DI. 那么这是DI的重大问题,它取决于DI。 Making everything work "AS IT SHOULD BE DONE" takes a lot of work. 让一切正常“因为它应该完成”需要做很多工作。 If you look at the new Action Injection is this not depending on DI if you use [FromServices] attribute. 如果您使用[FromServices]属性,如果查看新的Action Injection,则不依赖于DI。 Now DI people will say NO you are suppose to go with Factories not this stuff, but as you can see not even people making MVC did it right. 现在DI人们会说不,你不应该选择工厂而不是这个东西,但正如你所看到的那样,即使是制造MVC的人也没有做到这一点。 The problem of DI is visible in Filters as well look at what you need to do to get DI in a filter 在过滤器中可以看到DI的问题,看看在过滤器中获取DI需要做些什么

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.");
        }
    }
}

Where if you used SL you could have done this with var _logger = Locator.Get();. 如果您使用SL,则可以使用var _logger = Locator.Get();来完成此操作。 And then we come to the Views. 然后我们来到观点。 With all there good will regarding DI they had to use SL for the views. 有了DI的良好意愿,他们必须使用SL作为观点。 the new syntax @inject StatisticsService StatsService is the same as var StatsService = Locator.Get<StatisticsService>(); 新语法@inject StatisticsService StatsServicevar StatsService = Locator.Get<StatisticsService>(); . The most advertised part of DI is unit testing. DI中最广告的部分是单元测试。 But what people and up doing is just testing there mock services with no purpose or having to wire up there DI engine to do real tests. 但是人们和正在做的只是测试模拟服务没有任何目的或者必须在那里连接DI引擎来进行真正的测试。 And I know that you can do anything badly but people end up making a SL locator even if they don't know what it is. 而且我知道你可以做任何不好的事情,但即使他们不知道它是什么,人们也会最终制作SL定位器。 Where not a lot of people make DI without ever reading on it first. 没有很多人在没有先阅读的情况下制作DI。 My biggest problem with DI is that the user of the class must be aware of the inner workings of the class in other to use it. DI的最大问题是类的用户必须知道该类的内部工作方式才能使用它。
SL can be used in a good way and has some advantages most of all its simplicity. SL可以很好地使用,并且具有一些优点,最重要的是它的简单性。

For DI, do you need to have a hard reference to the injected type assembly? 对于DI,您是否需要对注入型组件进行硬引用? I don't see anyone talking about that. 我没有看到有人在谈论这个问题。 For SL, I can tell my resolver where to load my type dynamically when it needed from a config.json or similar. 对于SL,我可以告诉我的解析器在需要时从config.json或类似地动态加载我的类型。 Also, if your assembly contains several thousand types and their inheritance, do you need thousands cascading call to the service collection provider to register them? 此外,如果您的程序集包含数千种类型及其继承,您是否需要对服务集合提供程序进行数千次级联调用才能注册它们? That's where I do see much talk about. 这就是我看到很多话题的地方。 Most are talking about the benefit of DI and what it is in general, when it comes to how to implement it in .net, they presented with an extension method for adding reference to a hard linked types assembly. 大多数人都在谈论DI的好处及其一般情况,当谈到如何在.net中实现它时,他们提出了一种扩展方法,用于添加对硬链接类型程序集的引用。 That's not very decoupling to me. 这对我来说并不是很脱钩。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM