使用Ninject和ServiceLocater模式 - 好的或坏的

时间:2014-09-03 23:39:03

标签: c# dependency-injection dependencies ninject modular-design

我即将习惯Ninject。我理解依赖注入的原理,我知道如何使用Ninject。但我现在有点困惑。在服务定位器模式方面,意见分歧。

我的应用程序基于严格模块化的基础。我尝试尽可能多地使用构造函数注入,这很好用,虽然它有点混乱(在我看来)。

现在,当扩展(外部代码)想要从这个系统中受益时,是否需要访问内核?我的意思是,现在我有一个静态类,可以访问我的应用程序的所有子系统。另一种方法是访问内核(服务定位器模式)并从中获取子系统依赖关系。 在这里,我可以轻松避免访问内核或更明确,不允许依赖内核。

但是如果扩展现在想要使用我的应用程序的任何组件(接口),从任何子系统,它将需要访问内核才能解决它们,因为Ninject不会自动解析,只要你&# 39;不要使用" kernel.Get()",对吧?

Peww,以一种可以理解的方式解释这个问题真的很困难。我希望你们能得到我瞄准的目标。

为什么会如此"糟糕"对内核或它的包装有依赖吗?我的意思是,你不能避免所有依赖。例如,我仍然拥有我的" Core"允许访问所有子系统的类。 如果扩展程序想要注册它自己的模块以供进一步使用,该怎么办?

我找不到任何好的答案,为什么这应该是一个糟糕的方法,但我经常读它。此外,据说Ninject不使用这种方法,不像Unity或类似的框架。

谢谢:)

2 个答案:

答案 0 :(得分:4)

有关于此的宗教战争......

当你提到服务定位器时,人们首先说的是:“但如果我想更换我的容器怎么办?”。鉴于“适当的”服务定位器可能足够抽象以允许您切换底层容器,因此该参数几乎总是无效的。

也就是说,根据我的经验,使用服务定位器会使代码难以使用。您的服务定位器必须在任何地方传递,然后您的代码紧密耦合到服务定位器的存在

当您使用服务定位器时,您有两个主要选项可以将模块维护为“解耦”(这里使用的松散)方式。

选项1:

将您的定位器传递到需要它的所有内容中。从本质上讲,这意味着您的代码会成为很多这类代码:

var locator = _locator;

var customerService = locator.Get<ICustomerService>();
var orders = customerService.GetOrders(locator, customerId); // locator again

// .. further down..
var repo = locator.Get<ICustomerRepository>();
var orderRepo = locator.Get<IOrderRepository>();

// ...etc...

选项2:

将所有代码粉碎到一个程序集中,并在某处提供public static服务定位器。这更糟糕..最终与上面相同(只需直接调用服务定位器)。

Ninject是幸运的(幸运的是我的意思是 - 在Remo中有一个很好的维护者/扩展者)因为它有一堆扩展,允许你在系统的几乎所有部分充分利用控制反转,消除了我上面展示的代码。

答案 1 :(得分:2)

这有点违反了SO的政策,但为了延伸Simon的回答,我将引导您阅读Mark Seeman的优秀博文:http://blog.ploeh.dk/2010/02/03/ServiceLocatorisanAnti-Pattern/ 博客文章的一些评论也非常有趣。

现在用ninject和扩展来解决你的问题 - 我假设你在编写它们时你不知道/组合根 - 我想指出NinjectModule。 Ninject已经具有这样一种可扩展性机制,它与所有ninject.extension.XYZ dll一起使用很多。

您要做的是在扩展程序中实施一些FooExtensionModule : NinjectModule类。模块包含Bind方法。现在,您将告诉ninject从某些.dll加载所有模块。那就是它。

此处更详细地解释了这一点:https://github.com/ninject/ninject/wiki/Modules-and-the-Kernel

缺点:

  • 扩展取决于Ninject
    • 您可能需要在更新ninject&#34;
    • 时重新编译扩展程序
    • 随着软件的发展,切换DI容器会越来越昂贵
  • 使用Rebind时可能会出现难以追查的问题(无论您是否使用模块都是如此。)
  • 特别是当扩展程序开发人员不了解其他扩展程序时,他们可能会创建相同或冲突的绑定(例如.Bind<string>().ToConst("Foo").Bind<string>().ToConst("Bar"))。同样,当你使用模块时,也会出现这种情况,但扩展会增加一层复杂性。

优势:   - 简单而重要的是,没有额外的复杂/抽象层,你需要将容器抽象出来。

我在一个不太小的应用程序(15k单元/组件测试)中使用了NinjectModule方法并取得了很大的成功。

如果您只需要简单的绑定,例如.Bind<IFoo>().To<Foo>()没有范围等等,您也可以考虑使用更简单的系统,例如在类上放置属性,扫描这些属性,以及在组合根中创建绑定。这种方法的功能不那么强大,但正因为如此,便携式和#34; (也适用于其他DI容器)。

依赖注入延迟实例化

组合根的想法是,(只要有可能)你一次创建所有对象(整个对象图)。例如,在Main方法中,您可能有kernel.Get<IMainViewModel>().Show()

然而,有时这是不可行或不合适的。在这种情况下,您将需要使用工厂。在stackoverflow上已经有了很多关于这方面的答案。 有三种基本类型:

  • 要创建Foo的实例,该实例需要Dependency1Dependency2(注入ctor)的实例,请创建一个类FooFactory,它将获得{{1}的一个实例}和Dependency1 ctor注入了自己。然后,Dependency2方法会执行FooFactory.Create
  • 使用ninject.extensions.Factory
    • 使用new Foo(this.dependency1, this.dependency2)作为工厂:您可以注入Func<Foo>,然后调用它来创建Func<Foo>的实例。
    • 使用接口和Foo绑定(我推荐这种方法。更清晰的代码,更好的可测试性)。例如:.ToFactory()方法IFooFactory。将其绑定为:Foo Create()

替换实现的扩展

恕我直言,这不是依赖注入容器的目标之一。这并不意味着它是不可能的,但这只是意味着你必须自己解决这个问题。

使用ninject最简单的方法是使用Bind<IFooFactory>().ToFactory();。但是,如前所述,这有点脆弱。如果.Rebind<IFoo>().To<SomeExtensionsFoo>()Bind以错误的顺序执行,则会失败。如果有多个Rebind,那么最后一个会赢 - 但是它是正确的吗?

因此,让我们更进一步。想象:

Rebind

您可以设计自己的自定义`.Bind<IFoo>().To<SomeExtensionsFoo>().WhenExtensionEnabled<SomeExtension>();` 扩展方法,该方法扩展了WhenExtensionEnabled<TExtension>()语法方法。 您必须设计一种方法来检测扩展是否已启用。