使用Ninject,MVC 3和使用服务定位器模式进行依赖注入

时间:2011-01-05 17:53:57

标签: dependency-injection asp.net-mvc-3 ninject service-locator abstract-factory

自从我在另一个stackoverflow问题上找到答案(我现在得到的确切的答案)时,一直困扰着我的事情,其中​​用户说出了“如果您正在调用服务定位器,那么你正在做错。

这是一个声誉很高的人(我认为是十万人)所以我倾向于认为这个人可能知道他们在谈论什么。自从我第一次开始学习它以及它与单元测试的关系以及什么不是,我一直在为我的项目使用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);
    }
}

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

由于ModelBinder实现在Binder首次第一次时需要一个新实例,因此无法在构造函数上使用依赖注入来实现此特定实现。

在我的很多课程中都是这样的。另一个例子是Cache Expiration进程,它在我的网站中缓存对象到期时运行一个方法。我运行了一堆数据库调用,什么不是。我也在使用服务定位器来获得所需的依赖关系。

我最近遇到的另一个问题(我在这里发布了一个问题)是我的所有控制器都需要一个IDataContext实例,我使用了DI - 但一个动作方法需要一个不同的实例IDataContext。幸运的是,Ninject通过命名的依赖来救援。然而,这感觉就像一个kludge,而不是一个真正的解决方案。

我认为我至少理解了分离关注的概念,但是我理解依赖注入和服务定位器模式似乎存在根本性的错误 - 我不知道那是什么。< / p>

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

  1. 何时不接受?
  2. 当我重新考虑如何使用服务定位器模式时,我应该注意哪种模式?
  3. 我的ModelBinder实现错了吗?如果是这样,我需要学习什么才能修复它?
  4. 在另一个问题中,用户Mark Seemann推荐了一个抽象工厂 - 这与此有何关系?
  5. 我想就是这样 - 我无法想到任何其他问题来帮助我理解,但我们非常感谢任何额外的信息。

    我知道DI可能不是解决所有问题的方法,而且我可能会如何实施它,但是,它似乎按照我预期的方式进行单元测试,而不是。

    我不是在寻找代码来修复我的示例实现 - 我正在寻找学习,寻找解释以解决我的错误理解。

    我希望stackoverflow.com能够保存草稿问题。我也希望无论谁回答这个问题都能获得适当的声誉来回答这个问题,因为我觉得我要求的很多。提前谢谢。

1 个答案:

答案 0 :(得分:14)

请考虑以下事项:

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

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

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

您可以随时使用IoC / DI和SL的复合设计,就像使用Common Services Locator一样。您可以通过DI连接组件,但通过SL公开。您甚至可以将组合投入混合并使用类似Managed Extensibility Framework(它本身支持DI,但也可以连接到其他IoC容器或服务定位器)。这是一个很大的设计选择,通常我的建议是尽可能用于IoC / DI。

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

使用抽象工厂模式,工厂专门创建特定类型。您没有注册解析类型,实际上您注册了一个抽象工厂,并构建了您可能需要的任何类型。使用服务定位器,它旨在定位服务并返回这些实例。类似于会议的观点,但行为却截然不同。