Dependency Injection vs Service Location
我目前正在权衡DI和SL之间的优缺点。但是,我发现自己在下面的第22条中,这意味着我应该只使用SL来处理所有事情,并且只在每个类中注入一个IOC容器。
DI渔获量22:
有些依赖项,比如log4net,根本不适合DI。我称之为这些元依赖,并认为它们对于调用代码应该是不透明的。我的理由是,如果一个简单的类"d"最初是在没有日志记录的情况下实现的,然后增长为需要日志记录,那么依赖类"a"、"b"和"c"现在必须以某种方式获得这个依赖项,并将其从"a"传递到"d"(假设"a"组合"b"、"b"组合"c"等等)。我们现在做了大量的代码更改,仅仅是因为我们需要登录一个类。
因此,我们需要一种不透明的机制来获取元依赖性。需要注意的两个方面是:singleton和sl。前者有已知的局限性,主要是关于严格的作用域功能:最多,singleton将使用存储在应用程序作用域(即静态变量)中的抽象工厂。这允许一些灵活性,但并不完美。
更好的解决方案是将IOC容器注入此类类中,然后在该类中使用sl来解析容器中的这些元依赖项。
因此,第22条是:因为类现在正被注入一个IOC容器,那么为什么不使用它来解析所有其他依赖项呢?
我非常感谢你的想法:)
Because the class is now being injected with an IoC container, then why not use it to resolve all other dependencies too?
使用服务定位器模式完全破坏了依赖注入的一个主要点。依赖注入的要点是使依赖显式化。一旦通过不在构造函数中显式地设置参数来隐藏这些依赖项,就不再进行完整的依赖项注入。
这些都是名为
错误:
1 2 3 |
错误:
1 2 3 | public Foo() { this.bar = ServiceLocator.Resolve<Bar>(); } |
错误:
1 2 3 | public Foo(ServiceLocator locator) { this.bar = locator.Resolve<Bar>(); } |
正确的:
1 2 3 | public Foo(Bar bar) { this.bar = bar; } |
只有后者才明确依赖于
至于日志记录,有一个正确的方法可以做到这一点,而不会渗透到您的域代码中(不应该这样做,但如果这样做了,那么您将使用依赖注入周期)。令人惊讶的是,IOC容器可以帮助解决这个问题。从这里开始。
服务定位器是一种反模式,其原因在http://blog.ploeh.dk/2010/02/03/servicelocatorisanantpattern.aspx中有很好的描述。在日志记录方面,您可以像对待任何其他依赖一样将其视为依赖项,并通过构造函数或属性注入注入抽象。
与log4net的唯一区别是,它需要使用该服务的调用方的类型。使用ninject(或其他容器),如何找到请求服务的类型?描述如何解决此问题(它使用ninject,但适用于任何IOC容器)。
或者,您可以将日志记录看作是一个跨领域的问题,这不适合与业务逻辑代码混合使用,在这种情况下,您可以使用由许多IOC容器提供的拦截。http://msdn.microsoft.com/en-us/library/ff647107.aspx描述了使用带有Unity的拦截。
我认为这要看情况而定。有时一个更好,有时另一个更好。但我想说的是,将军,我更喜欢DI。原因不多。
当依赖项以某种方式注入组件时,可以将其视为其接口的一部分。因此,组件的用户更容易提供这些依赖项,因为它们是可见的。在注入SL或静态SL的情况下,依赖关系是隐藏的,组件的使用有点困难。
注入的依赖性对于单元测试更好,因为您可以简单地模拟它们。对于SL,您必须再次设置定位器+模拟依赖项。所以这是更多的工作。
有时日志记录可以使用AOP实现,这样它就不会与业务逻辑混合。
否则,选项包括:
- 使用一个可选的依赖项(如setter属性),对于单元测试,您不会注入任何记录器。如果您在生产中运行,IOC容器将自动为您设置它。
- 当你有一个几乎所有应用程序对象都在使用的依赖项时("logger"对象是最常见的例子),这是少数单例反模式成为良好实践的情况之一。有些人把这些"优秀的单身汉"称为一种环境背景:http://aabs.wordpress.com/2007/12/31/the-ambient-context-design-pattern-in-net/
当然,这个上下文必须是可配置的,以便您可以使用存根/模拟进行单元测试。AmbientContext的另一个建议用法是将当前日期/时间提供程序放在那里,这样您就可以在单元测试期间将其截短,并根据需要加速时间。
我知道这个问题有点老,我只是想给我一些建议。
事实上,10次中有9次你真的不需要SL,应该依赖DI。但是,在某些情况下,您应该使用SL。我发现自己使用SL(或其变体)的一个领域是游戏开发。
SL的另一个优势(在我看来)是能够绕过
下面是一个例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | internal sealed class SomeClass : ISomeClass { internal SomeClass() { // Add the service to the locator ServiceLocator.Instance.AddService<ISomeClass>(this); } // Maybe remove of service within finalizer or dispose method if needed. internal void SomeMethod() { Console.WriteLine("The user of my library doesn't know I'm doing this, let's keep it a secret"); } } public sealed class SomeOtherClass { private ISomeClass someClass; public SomeOtherClass() { // Get the service and call a method someClass = ServiceLocator.Instance.GetService<ISomeClass>(); someClass.SomeMethod(); } } |
正如您所看到的,库的用户不知道这个方法是被调用的,因为我们没有DI,无论如何我们都不能。
这是关于"服务定位器是一个反模式"的马克西曼。我可能错了。但我想我也应该分享我的想法。
1 2 3 4 5 6 7 8 9 10 11 12 | public class OrderProcessor : IOrderProcessor { public void Process(Order order) { var validator = Locator.Resolve<IOrderValidator>(); if (validator.Validate(order)) { var shipper = Locator.Resolve<IOrderShipper>(); shipper.Ship(order); } } } |
orderProcessor的process()方法实际上没有遵循"控制反转"原则。它还打破了方法级别的单一责任原则。为什么一个方法应该关注实例化
对象(通过新的或任何S.L.类)它需要完成任何事情。
与使用process()方法创建对象不同,构造函数实际上可以拥有各个对象(读依赖项)的参数,如下所示。那么,服务定位器如何能与国际奥委会有所不同呢
容器。它也将有助于单元测试。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | public class OrderProcessor : IOrderProcessor { public OrderProcessor(IOrderValidator validator, IOrderShipper shipper) { this.validator = validator; this.shipper = shipper; } public void Process(Order order) { if (this.validator.Validate(order)) { shipper.Ship(order); } } } //Caller public static void main() //this can be a unit test code too. { var validator = Locator.Resolve<IOrderValidator>(); // similar to a IOC container var shipper = Locator.Resolve<IOrderShipper>(); var orderProcessor = new OrderProcessor(validator, shipper); orderProcessor.Process(order); } |
我们已经达成了一个折衷方案:使用DI,但是将顶级依赖项捆绑到一个对象中,避免在这些依赖项发生变化时重构地狱。
在下面的示例中,我们可以添加到"ServiceDependencies",而不必重构所有派生的依赖项。
例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | public ServiceDependencies{ public ILogger Logger{get; private set;} public ServiceDependencies(ILogger logger){ this.Logger = logger; } } public abstract class BaseService{ public ILogger Logger{get; private set;} public BaseService(ServiceDependencies dependencies){ this.Logger = dependencies.Logger; //don't expose 'dependencies' } } public class DerivedService(ServiceDependencies dependencies, ISomeOtherDependencyOnlyUsedByThisService additionalDependency) : base(dependencies){ //set local dependencies here. } |
我在Java中使用了谷歌Guice DI框架,发现它比测试更简单。例如,我需要每个应用程序(而不是类)一个单独的日志,进一步要求所有公共库代码在当前调用上下文中使用记录器。注入记录器使这成为可能。诚然,所有的库代码都需要更改:记录器被注入到构造函数中。起初,我拒绝了这种方法,因为需要进行所有的编码更改;最终我意识到这些更改有很多好处:
- 代码变得更简单了
- 代码变得更加健壮
- 类的依赖关系变得明显
- 如果有许多依赖项,这就清楚地表明类需要重构。
- 消除了静态单体
- 对会话或上下文对象的需求消失了
- 多线程变得更加容易,因为DI容器可以构建为只包含一个线程,从而消除无意中的交叉污染。
不用说,我现在是DI的忠实拥趸,除了最微不足道的应用程序外,其他所有应用程序都使用它。
如果示例仅将log4net作为依赖项,则只需执行以下操作:
1 |
没有必要注入依赖项,因为log4net通过将类型(或字符串)作为参数来提供粒度日志记录。
此外,DI与SL不相关。imho服务定位器的目的是解决可选的依赖关系。
如果SL提供了一个ILOG接口,我将写日志DAA。
我知道人们真的说DI是唯一好的IOC模式,但我不明白。我想卖一点SL。我将使用新的MVC核心框架向您展示我的意思。第一个DI引擎非常复杂。当人们说DI时,真正的意思是使用一些框架,比如Unity、Ninject、Autopac…这对你来说是一种沉重的负担,在那里SL可以像制造工厂级的课程一样简单。对于一个小型的快速项目来说,这是一个简单的方法,可以在不学习完整的DI框架的情况下完成IOC,虽然学习起来可能并不那么困难,但仍然如此。现在讨论一下DI可以变成的问题。我将使用MVC核心文档的报价。"ASP.NET核心是从一开始就设计来支持和利用依赖注入的。"大多数人都说关于DI"99%的代码库应该不知道您的IOC容器。"那么,如果只有1%的代码应该知道它,而不是旧的MVC支持DI,为什么他们需要从头开始设计呢?这是DI的大问题,它取决于DI。使一切"按应该做的"工作需要大量的工作。如果您查看新的动作注入,那么如果您使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | public class SampleActionFilterAttribute : TypeFilterAttribute { public SampleActionFilterAttribute():base(typeof(SampleActionFilterImpl)) { } private class SampleActionFilterImpl : IActionFilter { private readonly ILogger _logger; public SampleActionFilterImpl(ILoggerFactory loggerFactory) { _logger = loggerFactory.CreateLogger<SampleActionFilterAttribute>(); } public void OnActionExecuting(ActionExecutingContext context) { _logger.LogInformation("Business action starting..."); // perform some business logic work } public void OnActionExecuted(ActionExecutedContext context) { // perform some business logic work _logger.LogInformation("Business action completed."); } } } |
其中,如果使用SL,则可以使用var _logger=locator.get();。然后我们来看看风景。尽管有关于DI的良好意愿,他们不得不使用SL来查看这些视图。新语法