3

我快要习惯 Ninject 了。我了解依赖注入的原理,并且知道如何使用 Ninject。但我现在有点困惑。当涉及到服务定位器模式时,意见分歧。

我的应用程序建立在严格的模块化基础上。我尝试尽可能多地使用构造函数注入,并且效果很好,尽管它有点混乱(在我看来)。

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

但是,如果一个扩展现在想要从任何子系统使用我的应用程序的任何组件(接口),则需要访问内核才能解析它们,因为只要您不使用 Ninject 就不会自动解析“kernel.Get()”,对吧?

Peww,很难以一种可以理解的方式解释这一点。我希望你们能得到我的目标。

为什么对内核或其包装器的依赖如此“糟糕”?我的意思是,你不能避免所有的依赖。例如,我的“核心”类仍然有一个,它可以访问所有子系统。如果扩展想要注册它自己的模块以供进一步使用怎么办?

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

谢谢 :)

4

2 回答 2

4

有关于这个的宗教战争......

当您提到服务定位器时,人们会说的第一件事是:“但是如果我想更改我的容器怎么办?”。这个论点几乎总是无效的,因为“适当的”服务定位器可能足够抽象以允许您切换底层容器。

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

当您使用服务定位器时,您有两个主要选项来以“解耦”(此处松散使用..)方式维护模块。

选项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 中有一个很棒的维护者/扩展程序),因为它有一堆扩展,允许您在系统的几乎所有部分中充分利用控制反转,消除了我展示的那种代码以上。

于 2014-09-04T00:32:18.413 回答
2

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

现在要解决您的 ninject 和扩展问题 - 我假设您/在编写它们时的组合根不知道 - 我想指出NinjectModules. Ninject 已经具有这种可扩展性机制,它被所有ninject.extension.XYZdll 大量使用。

你要做的是FooExtensionModule : NinjectModule在你的扩展中实现一些类。模块包含Bind方法。现在您将告诉 ninject 从某些 .dll 加载所有模块。而已。

这里有更详细的解释:https ://github.com/ninject/ninject/wiki/Modules-and-the-Kernel

缺点:

  • 扩展依赖于 Ninject
    • 当您“更新 ninject”时,您可能需要重新编译您的扩展
    • 随着软件的发展,切换 DI 容器的成本会越来越高
  • 使用时Rebind可能会出现难以追踪的问题(无论您是否使用模块,情况都是如此。)
  • 尤其是当扩展开发人员不了解其他扩展时,他们可能会创建相同或冲突的绑定(例如.Bind<string>().ToConst("Foo")and .Bind<string>().ToConst("Bar"))。同样,当您使用模块时也是如此,但扩展增加了一层复杂性。

优点: - 简单明了,没有额外的复杂性/抽象层,您需要将容器抽象掉。

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

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

依赖注入延迟实例化

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

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

  • 要创建一个Foo需要Dependency1and实例的实例Dependency2(注入 ctor),请创建一个类FooFactory,该类获取一个实例Dependency1Dependency2ctor 注入本身。然后该FooFactory.Create方法将执行new Foo(this.dependency1, this.dependency2)
  • 使用ninject.extensions.Factory
    • 用作Func<Foo>工厂:您可以进行Func<Foo>注入,然后调用它来创建Foo.
    • 使用接口和.ToFactory()绑定(我推荐这种方法。更简洁的代码,更好的可测试性)。例如:IFooFactory使用方法Foo Create()。像这样绑定它:Bind<IFooFactory>().ToFactory();

替换实现的扩展

恕我直言,这不是依赖注入容器的目标之一。这并不意味着这是不可能的,而只是意味着你必须自己弄清楚。

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

所以让我们更进一步。想象:

`.Bind<IFoo>().To<SomeExtensionsFoo>().WhenExtensionEnabled<SomeExtension>();`

您可以设计自己的自定义WhenExtensionEnabled<TExtension>()扩展方法来扩展When(Func<bool> condition)语法方法。您必须设计一种方法来检测是否启用了扩展。

于 2014-09-04T04:50:33.373 回答