我即将习惯Ninject。我理解依赖注入的原理,我知道如何使用Ninject。但我现在有点困惑。在服务定位器模式方面,意见分歧。
我的应用程序基于严格模块化的基础。我尝试尽可能多地使用构造函数注入,这很好用,虽然它有点混乱(在我看来)。
现在,当扩展(外部代码)想要从这个系统中受益时,是否需要访问内核?我的意思是,现在我有一个静态类,可以访问我的应用程序的所有子系统。另一种方法是访问内核(服务定位器模式)并从中获取子系统依赖关系。 在这里,我可以轻松避免访问内核或更明确,不允许依赖内核。
但是如果扩展现在想要使用我的应用程序的任何组件(接口),从任何子系统,它将需要访问内核才能解决它们,因为Ninject不会自动解析,只要你&# 39;不要使用" kernel.Get()",对吧?
Peww,以一种可以理解的方式解释这个问题真的很困难。我希望你们能得到我瞄准的目标。
为什么会如此"糟糕"对内核或它的包装有依赖吗?我的意思是,你不能避免所有依赖。例如,我仍然拥有我的" Core"允许访问所有子系统的类。 如果扩展程序想要注册它自己的模块以供进一步使用,该怎么办?
我找不到任何好的答案,为什么这应该是一个糟糕的方法,但我经常读它。此外,据说Ninject不使用这种方法,不像Unity或类似的框架。
谢谢:)
答案 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
缺点:
Rebind
时可能会出现难以追查的问题(无论您是否使用模块都是如此。).Bind<string>().ToConst("Foo")
和.Bind<string>().ToConst("Bar")
)。同样,当你不使用模块时,也会出现这种情况,但扩展会增加一层复杂性。优势: - 简单而重要的是,没有额外的复杂/抽象层,你需要将容器抽象出来。
我在一个不太小的应用程序(15k单元/组件测试)中使用了NinjectModule
方法并取得了很大的成功。
如果您只需要简单的绑定,例如.Bind<IFoo>().To<Foo>()
没有范围等等,您也可以考虑使用更简单的系统,例如在类上放置属性,扫描这些属性,以及在组合根中创建绑定。这种方法的功能不那么强大,但正因为如此,便携式和#34; (也适用于其他DI容器)。
组合根的想法是,(只要有可能)你一次创建所有对象(整个对象图)。例如,在Main
方法中,您可能有kernel.Get<IMainViewModel>().Show()
。
然而,有时这是不可行或不合适的。在这种情况下,您将需要使用工厂。在stackoverflow上已经有了很多关于这方面的答案。 有三种基本类型:
Foo
的实例,该实例需要Dependency1
和Dependency2
(注入ctor)的实例,请创建一个类FooFactory
,它将获得{{1}的一个实例}和Dependency1
ctor注入了自己。然后,Dependency2
方法会执行FooFactory.Create
。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>()
语法方法。
您必须设计一种方法来检测扩展是否已启用。