ServiceLocator 是一种反模式吗? [英] Is ServiceLocator an anti-pattern?

查看:30
本文介绍了ServiceLocator 是一种反模式吗?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

最近我阅读了Mark Seemann 的关于服务的文章定位器反模式.

Recently I've read Mark Seemann's article about Service Locator anti-pattern.

作者指出了 ServiceLocator 是反模式的两个主要原因:

Author points out two main reasons why ServiceLocator is an anti-pattern:

  1. API 使用问题(我完全没问题)
    当类使用服务定位器时,很难看到它的依赖关系,因为在大多数情况下,类只有一个无参数构造函数.与 ServiceLocator 相比,DI 方法通过构造函数的参数显式公开依赖项,因此在 IntelliSense 中很容易看到依赖项.

  1. API usage issue (which I'm perfectly fine with)
    When class employs a Service locator it is very hard to see its dependencies as, in most cases, class has only one PARAMETERLESS constructor. In contrast with ServiceLocator, DI approach explicitly exposes dependencies via constructor's parameters so dependencies are easily seen in IntelliSense.

维护问题(这让我很困惑)
考虑下面的例子

Maintenance issue (which puzzles me)
Consider the following expample

我们有一个使用服务定位器方法的 'MyType' 类:

We have a class 'MyType' which employs a Service locator approach:

public class MyType
{
    public void MyMethod()
    {
        var dep1 = Locator.Resolve<IDep1>();
        dep1.DoSomething();
    }
}

现在我们要向类MyType"添加另一个依赖项

Now we want to add another dependency to class 'MyType'

public class MyType
{
    public void MyMethod()
    {
        var dep1 = Locator.Resolve<IDep1>();
        dep1.DoSomething();
            
        // new dependency
        var dep2 = Locator.Resolve<IDep2>();
        dep2.DoSomething();
    }
}

这就是我的误解开始的地方.作者说:

And here is where my misunderstanding starts. The author says:

判断您是否引入了重大更改变得更加困难.您需要了解使用 Service Locator 的整个应用程序,而编译器不会帮助您.

It becomes a lot harder to tell whether you are introducing a breaking change or not. You need to understand the entire application in which the Service Locator is being used, and the compiler is not going to help you.

但是等一下,如果我们使用 DI 方法,我们将在构造函数中引入具有另一个参数的依赖项(在构造函数注入的情况下).而且问题依然存在.如果我们可能忘记设置 ServiceLocator,那么我们可能会忘记在我们的 IoC 容器中添加一个新的映射,而 DI 方法也会有同样的运行时问题.

But wait a second, if we were using DI approach, we would introduce a dependency with another parameter in constructor (in case of constructor injection). And the problem will be still there. If we may forget to setup ServiceLocator, then we may forget to add a new mapping in our IoC container and DI approach would have the same run-time problem.

另外,作者提到了单元测试的困难.但是,我们不会对 DI 方法有问题吗?我们不需要更新所有实例化该类的测试吗?我们将更新它们以传递新的模拟依赖项,以使我们的测试可编译.我没有看到更新和时间花费有任何好处.

Also, author mentioned about unit test difficulties. But, won't we have issues with DI approach? Won't we need to update all tests which were instantiating that class? We will update them to pass a new mocked dependency just to make our test compilable. And I don't see any benefits from that update and time spending.

我不是要为 Service Locator 方法辩护.但是这种误解让我觉得我失去了一些很重要的东西.有人能打消我的疑虑吗?

I'm not trying to defend Service Locator approach. But this misunderstanding makes me think that I'm losing something very important. Could somebody dispel my doubts?

更新(摘要):

我的问题服务定位器是一种反模式吗"的答案真的要视情况而定.而且我绝对不会建议将它从您的工具列表中删除.当您开始处理遗留代码时,它可能会变得非常方便.如果您有幸处于项目的最初阶段,那么 DI 方法可能是更好的选择,因为它比 Service Locator 有一些优势.

The answer for my question "Is Service Locator an anti-pattern" really depends upon the circumstances. And I definitely wouldn't suggest to cross it out from your tool list. It might become very handy when you start dealing with legacy code. If you're lucky enough to be at the very beginning of your project then the DI approach might be a better choice as it has some advantages over Service Locator.

以下是说服我不要在我的新项目中使用 Service Locator 的主要区别:

And here are main differences which convinced me to not use Service Locator for my new projects:

  • 最明显也是最重要的:服务定位器隐藏类依赖
  • 如果您正在使用某些 IoC 容器,它可能会在启动时扫描所有构造函数以验证所有依赖项,并在缺少映射(或错误配置)时立即为您提供反馈;如果您将 IoC 容器用作服务定位器,则这是不可能的

有关详细信息,请阅读下面给出的优秀答案.

For details read excellent answers which are given below.

推荐答案

如果你将模式定义为反模式只是因为在某些情况下它不适合,那么它是反模式.但根据这种推理,所有模式也都是反模式.

If you define patterns as anti-patterns just because there are some situations where it does not fit, then YES it's an anti pattern. But with that reasoning all patterns would also be anti patterns.

相反,我们必须查看模式的有效用法,对于 Service Locator,有几个用例.但是,让我们先看看您提供的示例.

Instead we have to look if there are valid usages of the patterns, and for Service Locator there are several use cases. But let's start by looking at the examples that you have given.

public class MyType
{
    public void MyMethod()
    {
        var dep1 = Locator.Resolve<IDep1>();
        dep1.DoSomething();

        // new dependency
        var dep2 = Locator.Resolve<IDep2>();
        dep2.DoSomething();
    }
}

该类的维护噩梦是依赖项被隐藏.如果您创建并使用该类:

The maintenance nightmare with that class is that the dependencies are hidden. If you create and use that class:

var myType = new MyType();
myType.MyMethod();

如果使用服务位置隐藏它们,则您不明白它具有依赖项.现在,如果我们改为使用依赖注入:

You do not understand that it has dependencies if they are hidden using service location. Now, if we instead use dependency injection:

public class MyType
{
    public MyType(IDep1 dep1, IDep2 dep2)
    {
    }

    public void MyMethod()
    {
        dep1.DoSomething();

        // new dependency
        dep2.DoSomething();
    }
}

您可以直接发现依赖项,并且在满足它们之前不能使用这些类.

You can directly spot the dependencies and cannot use the classes before satisfying them.

在典型的业务应用程序中,出于这个原因,您应该避免使用服务位置.它应该是没有其他选择时使用的模式.

In a typical line of business application you should avoid the use of service location for that very reason. It should be the pattern to use when there are no other options.

没有

例如,如果没有服务位置,控制容器的反转将无法工作.这是他们在内部解决服务的方式.

For instance, inversion of control containers would not work without service location. It's how they resolve the services internally.

但更好的例子是 ASP.NET MVC 和 WebApi.您认为是什么使控制器中的依赖注入成为可能?没错——服务地点.

But a much better example is ASP.NET MVC and WebApi. What do you think makes the dependency injection possible in the controllers? That's right -- service location.

但是等一下,如果我们使用 DI 方法,我们会引入一个与构造函数中的另一个参数的依赖关系(如果构造函数注入).问题依然存在.

But wait a second, if we were using DI approach, we would introduce a dependency with another parameter in constructor (in case of constructor injection). And the problem will be still there.

还有两个更严重的问题:

There are two more serious problems:

  1. 使用服务位置,您还添加了另一个依赖项:服务定位器.
  2. 您如何确定依赖项应该具有哪个生命周期,以及应该如何/何时清理它们?

通过使用容器的构造函数注入,您可以免费获得.

With constructor injection using a container you get that for free.

如果我们可以忘记设置ServiceLocator,那么我们可能忘记添加一个新的我们的 IoC 容器中的映射和 DI 方法将具有相同的运行时问题.

If we may forget to setup ServiceLocator, then we may forget to add a new mapping in our IoC container and DI approach would have the same run-time problem.

确实如此.但是通过构造函数注入,您不必扫描整个类来找出缺少哪些依赖项.

That's true. But with constructor injection you do not have to scan the entire class to figure out which dependencies are missing.

并且一些更好的容器还在启动时验证所有依赖项(通过扫描所有构造函数).因此,对于这些容器,您会直接收到运行时错误,而不是在稍后的某个时间点.

And some better containers also validate all dependencies at startup (by scanning all constructors). So with those containers you get the runtime error directly, and not at some later temporal point.

另外,作者提到了单元测试的困难.但是,我们不会在 DI 方法上遇到问题吗?

Also, author mentioned about unit test difficulties. But, won't we have issues with DI approach?

没有.因为您不依赖于静态服务定位器.您是否尝试过使用静态依赖项进行并行测试?不好玩.

No. As you do not have a dependency to a static service locator. Have you tried to get parallel tests working with static dependencies? It's not fun.

这篇关于ServiceLocator 是一种反模式吗?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆