依赖注入(Dependency Injection)模式和服务定位器(Service Locator)模式有什么区别?

13 浏览
0 Comments

依赖注入(Dependency Injection)模式和服务定位器(Service Locator)模式有什么区别?

这两种模式似乎都是实现了控制反转原则。也就是说,一个对象不应该知道如何构造它的依赖关系。\n依赖注入(DI)似乎使用构造函数或setter来“注入”它的依赖关系。\n使用构造函数注入的示例:\n// Foo需要一个IBar\npublic class Foo\n{\n private IBar bar;\n public Foo(IBar bar)\n {\n this.bar = bar;\n }\n //...\n}\n服务定位器似乎使用一个“容器”,它连接它的依赖关系并为foo提供它的bar。\n使用服务定位器的示例:\n// Foo需要一个IBar\npublic class Foo\n{\n private IBar bar;\n public Foo()\n {\n this.bar = Container.Get();\n }\n //...\n}\n因为我们的依赖关系本身只是对象,这些依赖关系又有依赖关系,依此类推。因此,控制反转容器(或DI容器)诞生了。例如:Castle Windsor,Ninject,Structure Map,Spring等。\n但是控制反转/依赖注入容器看起来完全像一个服务定位器。称它为DI容器是不正确的吗?IOC/DI容器只是另一种类型的服务定位器吗?细微之处在于我们在有多个依赖关系时主要使用DI容器吗?

0
0 Comments

依赖注入(Dependency Injection)和服务定位器(Service Locator)是两种常见的软件设计模式。它们的出现原因是为了解决隐藏依赖关系和破坏封装性的问题。

服务定位器模式通过提供全局访问点来隐藏依赖关系。通过服务定位器获取连接时,无法通过查看对象来判断它是否与数据库等交互。此外,服务定位器破坏了封装性,因为它们提供了对其他对象依赖关系的访问。使用服务定位器时,很难为客户端对象的接口指定前置和后置条件,因为其实现的工作方式可以从外部进行干预。

相比之下,依赖注入通过显式地指定依赖关系来解决这些问题,尤其是构造函数注入。一旦对象的依赖关系被指定,它们就由对象本身控制。

文章中引用了一些相关的文章和观点,包括Steve Yegge的"Singletons Considered Stupid"和Miško Hevery的"Singletons are Pathological liars"。这些文章对服务定位器的问题进行了更深入的探讨和批评。

最后,文章中还提到了服务定位器不一定是单例的,并且可以通过工程化的方式抛出编译时错误来替代依赖注入的运行时错误。

依赖注入和服务定位器是为了解决隐藏依赖关系和破坏封装性的问题而出现的。依赖注入通过显式指定依赖关系来解决这些问题,而服务定位器则通过提供全局访问点来隐藏依赖关系。两种模式各有优缺点,具体使用时需要根据实际情况进行选择。

0
0 Comments

在使用服务定位器时,每个类都将依赖于您的服务定位器。但是使用依赖注入则不同,依赖注入器通常只在启动时调用一次,将依赖注入到某个主类中。这个主类所依赖的类会递归地注入其依赖,直到形成一个完整的对象图。

当你的依赖注入器看起来像一个服务定位器,类直接调用注入器时,它可能不是一个依赖注入器,而是一个服务定位器。

但是当你需要在运行时创建对象时,你该如何处理呢?如果你使用"new"手动创建对象,你就不能使用依赖注入。如果你调用依赖注入框架来帮助,你就破坏了这个模式。那么还有什么选择呢?

我曾经遇到同样的问题,决定注入特定类的工厂。虽然不太美观,但完成了工作。希望能看到一个更美观的解决方案。

直接比较的链接:martinfowler.com/articles/…

如果我需要在运行时构建新的对象,我会注入一个用于创建相关对象的抽象工厂。这在这种情况下类似于注入服务定位器,但提供了一个具体、统一、编译时的界面来构建相关对象,并明确了依赖关系。

0
0 Comments

依赖注入(DI)和服务定位器(Service Locator)是两种常用的软件设计模式。它们的区别在于,DI模式下,类会被直接传入它所依赖的对象,而无需关心这些对象从哪里来;而在服务定位器模式下,类需要通过服务定位器来获取它所依赖的对象。

这两种模式的区别可能看起来细微,但即使使用服务定位器,类仍然负责创建它所依赖的对象,只不过是通过服务定位器来实现而已。而在DI模式下,类会直接获得它的依赖对象,无需关心这些对象来自何方。这种区别的一个重要结果是,DI的示例在单元测试时更容易,因为可以传入模拟实现的依赖对象。如果需要的话,也可以将两种模式结合使用,通过注入服务定位器(或者工厂)来实现。

此外,构建一个类时可以同时使用两种模式。默认构造函数可以使用服务定位器来获取依赖对象,并将它们传递给接收这些依赖对象的“真正”构造函数。这样可以兼顾两种模式的优势。

服务定位器负责实例化给定依赖(插件)的正确实现,而在DI模式下,DI容器负责这个任务。

然而,对于服务定位器来说,类仍然需要了解服务定位器的存在,这增加了两个依赖关系。而且通常情况下,我见过服务定位器会委托给DI容器进行查找,特别是对于需要服务支持的瞬态对象而言。

我并没有说服务定位器会委托给DI容器。这两种模式是互斥的,正如“官方”文章中所描述的。对我来说,在实践中,服务定位器比DI模式有一个巨大的优势:使用DI容器容易滥用(我反复见过这种情况),而使用服务定位器则不会。

我却有完全相反的经验,我见过服务定位器被完全滥用,特别是当你编写更多面向组件/服务的代码时,而不是面向对象的代码,更注重行为和状态的分离。服务定位器模式对于喜欢传统面向对象编程的人来说非常流行(例如Active Record模式与DAO/Repository模式)。在过去,这对我来说并不可扩展,尤其是在消息传递架构中。你必须向我展示滥用的证据。

关于DI滥用的证据,我不知道是否有公开可用的代码库。在我所见过或参与的商业应用程序(Java和C#.NET)中,DI的使用似乎仅仅是出于政治原因(一些公司甚至“强制”所有新项目使用某个DI容器)。有数百个具有单独接口但只有一个实现的组件,而且并没有真正期望会出现其他实现。从我的经验来看,这在IT行业相当普遍,但也许你比我更幸运一些。

啊,是的...我见过这种情况发生。你会遇到接口地狱的问题。我几乎从不使用接口,但是出于某种原因,人们认为你需要它们来实现良好的设计。而且人们认为DI需要它们用于AOP(因为动态代理),但这是不正确的。说到AOP,我可以使用AspectJ来避免DI和服务定位器模式的大部分问题,但这是另一个故事了...

大多数DI容器都允许将一个依赖注册为它自身。这样可以避免仅仅为了能够注入而创建一个接口。如果有必要,我经常会这样使用,同时还要小心将方法定义为虚拟的,以便它们仍然可以被模拟框架轻松地模拟。

"DI的一个重要结果是,它很容易进行单元测试,因为你可以传入模拟实现的依赖对象。"这是不正确的。在你的单元测试中,可以调用服务定位器容器中的注册函数,将模拟对象轻松地添加到注册表中。

但是通过构造函数注入,我不需要担心任何多余的配置;我认为这更容易。

在我的实现中,如果我在测试中构建了一个模拟对象,我只需要像这样注册它:Container::register('class_name', mockObject);。这是在测试中额外的一行代码,而不是简单地将模拟对象传递给构造函数,但我不会说构造函数注入使得测试更容易。这取决于你对容器类的实现方式。

好的,现在将其更改为使用其他定位器或更改定位器的注册方式。看到了吗,这就是使用服务定位器的问题 - 你将实现和测试耦合到了实际工作中不需要的东西上,而只是它的配置信息。

我想表达的是,使用服务定位器并不会使得测试类变得困难。

这个回答并没有描述服务定位器的本质和为什么它是个问题。一个类如果在内部创建它的依赖关系,并不意味着它就是一个服务定位器。请参考下面Jeff Sternal的回答。

0