ServiceLocator是否是反模式? [英] Is ServiceLocator an anti-pattern?

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

问题描述

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



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


  1. API使用问题(我完全可以使用)

    当课程使用Service定位器时,很难将其依赖项看作大多数情况下,类只有一个PARAMETERLESS构造函数。
    与ServiceLocator相反,DI方法通过构造函数的参数明确地暴露依赖关系,因此IntelliSence中可以轻松看到依赖关系。


  2. 维护问题(这让我困惑)

    考虑以下示例


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

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

现在我们要添加另一个依赖关系到类MyType '

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

//新的依赖关系
var dep2 = Locator.Resolve< IDep2>();
dep2.DoSomething();
}
}

这是我的误解开始。作者说:


很难判断您是否引入了一个突破性的变化。您需要了解正在使用服务定位器的整个应用程序,并且编译器不会帮助您。


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



另外,作者提到单元测试困难。但是,我们不会有DI方法的问题吗?我们不需要更新实例化该类的所有测试?我们将更新它们以传递一个新的嘲弄依赖,以使我们的测试可编译。我没有看到更新和时间支出的任何好处。



我不是想保卫服务定位器的方法。但这个误会让我觉得我失去了一些非常重要的东西。有人可以消除我的疑虑吗?



更新(摘要):



对于我的问题服务定位器反模式是否真的取决于具体情况。我绝对不会建议你从你的工具列表中交出来。当您开始处理遗留代码时,可能会变得非常方便。如果你幸运地在项目的开始,那么DI方法可能是一个更好的选择,因为它比服务定位器有一些优势。



这里是最重要的是:服务定位器隐藏类依赖性 >

  • 如果您正在使用一些IoC容器,它可能会在启动时扫描所有构造函数,以验证所有依赖关系,并立即为失踪映射(或错误配置)提供反馈;如果您使用IoC容器作为服务定位器,这是不可能的。



  • 有关详细信息,请阅读下面给出的优秀答案。 p>

    解决方案

    如果您将模式定义为反模式,只是因为有些情况不合适,那么YES就是反模式。但是,推理所有模式也将是反模式。



    相反,我们必须查看是否有有用的模式,而对于服务定位器有几种用例。但是我们先来看看你给出的例子。

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

    //新的依赖关系
    var dep2 = Locator.Resolve< IDep2>();
    dep2.DoSomething();
    }
    }

    该类的维护噩梦是依赖关系是隐。如果您创建并使用该类:

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

    如果使用服务位置隐藏它们,则不了解它是否具有依赖关系。现在,如果我们改为使用依赖注入:

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

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

    //新的依赖关系
    dep2.DoSomething();
    }
    }

    您可以直接发现依赖关系,不能使用类在满足他们之前。



    在一个典型的业务应用程序中,您应该避免使用服务位置,因为这个原因。



    模式是否为反模式?



    没有。



    例如,如果没有服务位置,控制容器的反转将不起作用。这是他们如何在内部解决服务。



    但是一个更好的例子是ASP.NET MVC和WebApi。你认为在控制器中可以实现依赖注入?这是正确的 - 服务位置。



    您的问题




    但等一下如果我们使用DI方法,我们将在构造函数中引用
    依赖关系(在
    构造函数注入的情况下)。问题仍然存在。


    还有两个更严重的问题:


    1. 在服务位置,您还添加了另一个依赖关系:服务定位器。

    2. 你如何知道依赖关系应该有哪一个生命周期,以及如何/何时清理?

    使用容器注入构造函数,可以免费获得。


    如果我们可以
    忘记设置ServiceLocator那么我们可能会忘记在我们的Io​​C容器中添加一个新的
    映射,并且DI方法将具有相同的
    运行时问题。


    这是真的。但是通过构造器注入,您不必扫描整个类,以确定哪些依赖关系丢失。



    一些更好的容器也会在启动时验证所有依赖项(通过扫描所有构造函数)。所以使用这些容器可以直接得到运行时错误,而不是稍后的时间点。


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


    否。因为您没有对静态服务定位器的依赖。您是否尝试使用静态依赖关系进行并行测试?这不是很有趣。


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

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

    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 expose dependencies via constructor's parameters so dependencies are easy seen in IntelliSence.

    2. Maintenance issue (which puzzles me)
      Consider the following expample

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

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

    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 my misunderstanding start. Author says:

    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.

    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.

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

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

    UPDATE (SUMMARY):

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

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

    • Most obvious and important: Service Locator hides class dependencies
    • If you are utilizing some IoC container it will likely scan all constructor at startup to validate all dependencies and give you immediate feedback on missing mappings (or wrong configuration); this is not possible if you're using your IoC container as Service Locator

    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.

    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.

    Is the pattern an anti-pattern?

    No.

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

    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.

    Your questions

    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. With service location you are also adding another dependency: The service locator.
    2. How do you tell which lifetime the dependencies should have, and how/when they should get cleaned up?

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

    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.

    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天全站免登陆