14

自从我阅读了关于另一个 stackoverflow 问题的答案(现在我不知道确切的问题)的答案以来,一直困扰着我的事情是,用户说“如果您正在调用服务定位器,那么您做错了。

这是一个声望很高的人(我想有几十万)所以我倾向于认为这个人可能知道他们在说什么。自从我第一次开始了解 DI 以及它与单元测试的关系以及不相关的情况以来,我一直在我的项目中使用 DI。这是我现在相当舒服的事情,我我知道我在做什么。

但是,有很多地方我一直在使用服务定位器来解决项目中的依赖关系。一旦主要示例来自我的 ModelBinder 实现。

典型模型粘合剂的示例。

public class FileModelBinder : IModelBinder {
    public object BindModel(ControllerContext controllerContext,
                            ModelBindingContext bindingContext) {
        ValueProviderResult value = bindingContext.ValueProvider.GetValue("id");

        IDataContext db = Services.Current.GetService<IDataContext>();
        return db.Files.SingleOrDefault(i => i.Id == id.AttemptedValue);
    }
}

不是真正的实现——只是一个简单的例子

由于第一次请求Binder 时 ModelBinder 实现需要一个新实例,因此不可能在此特定实现的构造函数上使用依赖注入。

我的很多课都是这样。另一个例子是缓存过期进程,只要缓存对象在我的网站中过期,它就会运行一个方法。我运行了一堆数据库调用,什么都没有。我也在使用服务定位器来获取所需的依赖项。

我最近遇到的另一个问题(我在这里发布了一个问题)是我所有的控制器都需要一个 IDataContext 的实例,我使用 DI 来处理它——但是一个操作方法需要一个不同的 IDataContext 实例。幸运的是,Ninject 带来了一个命名依赖项。然而,这感觉像是一个杂牌,而不是一个真正的解决方案。

我认为我至少对关注点分离的概念理解得相当好,但我对依赖注入和服务定位器模式的理解似乎存在根本性的错误——我不知道那是什么。

我目前理解它的方式 - 这也可能是错误的 - 是,至少在 MVC 中,ControllerFactory 为 Controller 查找构造函数并调用 Service Locator 本身以获取所需的依赖项,然后将它们传递进去。但是,我可以理解,不是所有的类和什么都没有工厂来创建它们。所以在我看来,一些服务定位器模式是可以接受的......但是......

  1. 什么时候不能接受?
  2. 当我应该重新考虑如何使用服务定位器模式时,我应该注意哪种模式?
  3. 我的 ModelBinder 实现错了吗?如果是这样,我需要学习什么来解决它?
  4. 在另一个与该用户类似的问题中,Mark Seemann推荐了一个抽象工厂——这有什么关系?

我想就是这样 - 我真的想不出任何其他问题来帮助我理解,但非常感谢任何额外的信息。

我知道 DI 可能不是所有问题的答案,而且我在实现它的方式上可能有些过火,但是,它似乎以我期望的方式工作,与单元测试一起工作,而不是什么。

我不是在寻找代码来修复我的示例实现——我在寻找学习,寻找解释来修复我有缺陷的理解。

我希望 stackoverflow.com 能够保存草稿问题。我也希望回答这个问题的人在回答这个问题时获得适当的声誉,因为我认为我要求很多。提前致谢。

4

1 回答 1

15

考虑以下:

public class MyClass
{
  IMyInterface _myInterface;
  IMyOtherInterface _myOtherInterface;

  public MyClass(IMyInterface myInterface, IMyOtherInterface myOtherInterface)
  {
    // Foo

    _myInterface = myInterface;
    _myOtherInterface = myOtherInterface;
  }
}

通过这种设计,我能够表达我的类型的依赖要求。类型本身不负责知道如何实例化任何依赖项,它们由使用的任何解析机制(通常是 IoC 容器)提供(注入)给它。然而:

public class MyClass
{
  IMyInterface _myInterface;
  IMyOtherInterface _myOtherInterface;

  public MyClass()
  {
    // Bar

    _myInterface = ServiceLocator.Resolve<IMyInterface>();
    _myOtherInterface = ServiceLocator.Resolve<IMyOtherInterface>();
  }
}

我们的类现在依赖于创建特定的实例,但是通过委托给服务定位器。从这个意义上说,服务位置可以被认为是一种反模式,因为您没有暴露依赖关系,但是您允许可以通过编译捕获的问题冒泡到运行时。(一个很好的阅读是here)。你隐藏了复杂性。

两者之间的选择实际上取决于您的建筑基础及其提供的服务。通常,如果您从头开始构建应用程序,我会一直选择 DI。它提高了可维护性,促进了模块化并使测试类型变得更加容易。但是,以 ASP.NET MVC3 为例,您可以轻松地将 SL 实现为融入设计。

您始终可以进行复合设计,在其中可以将 IoC/DI 与 SL 结合使用,就像使用Common Services Locator一样。您的组件可以通过 DI 连接,但通过 SL 暴露。您甚至可以将组合加入其中并使用托管可扩展性框架(它本身支持 DI,但也可以连接到其他 IoC 容器或服务定位器)。这是一个很大的设计选择,通常我的建议是尽可能使用 IoC/DI。

我不会说您的具体设计是错误的。在这种情况下,您的代码不负责创建模型绑定器本身的实例,这取决于框架,因此您无法控制它,您对服务定位器的使用可能很容易更改为访问 IoC 容器。 但是在 IoC 容器上调用 resolve 的动作……你不会考虑那个服务位置吗?

使用抽象工厂模式,工厂专门用于创建特定类型。您没有注册类型以进行解析,您实际上注册了一个抽象工厂并构建您可能需要的任何类型。使用服务定位器,它旨在定位服务并返回这些实例。从约定的角度来看相似,但在行为上却大不相同。

于 2011-01-05T18:24:00.393 回答