这两种模式看起来都像是控制反转原理的实现。也就是说,对象不应该知道如何构建其依赖关系。
依赖注入(DI)似乎使用构造函数或设置器来“注入”它的依赖项。
使用构造函数注入的示例:
//Foo Needs an IBar
public class Foo
{
private IBar bar;
public Foo(IBar bar)
{
this.bar = bar;
}
//...
}
服务定位器似乎使用“容器”,它连接其依赖项并为 foo 提供它的栏。
使用服务定位器的示例:
//Foo Needs an IBar
public class Foo
{
private IBar bar;
public Foo()
{
this.bar = Container.Get<IBar>();
}
//...
}
因为我们的依赖项本身就是对象,所以这些依赖项具有依赖项,这些依赖项具有更多的依赖项,依此类推。于是,控制反转容器(或DI容器)诞生了。例如:Castle Windsor、Ninject、Structure Map、Spring 等)
但是 IOC/DI 容器看起来完全就像一个服务定位器。称其为 DI 容器是一个坏名字吗? IOC/DI 容器只是另一种类型的服务定位器吗?当我们有很多依赖项时,我们主要使用 DI 容器,这是否有细微差别?
Both patterns seem like an implementation of the principle of inversion of control. That is, that an object should not know how to construct its dependencies.
Dependency Injection (DI) seems to use a constructor or setter to "inject" it's dependencies.
Example of using Constructor Injection:
//Foo Needs an IBar
public class Foo
{
private IBar bar;
public Foo(IBar bar)
{
this.bar = bar;
}
//...
}
Service Locator seems to use a "container", which wires up its dependencies and gives foo it's bar.
Example of using a Service Locator:
//Foo Needs an IBar
public class Foo
{
private IBar bar;
public Foo()
{
this.bar = Container.Get<IBar>();
}
//...
}
Because our dependencies are just objects themselves, these dependencies have dependencies, which have even more dependencies, and so on and so forth. Thus, the Inversion of Control Container (or DI Container) was born. Examples: Castle Windsor, Ninject, Structure Map, Spring, etc.)
But a IOC/DI Container looks exactly like a Service Locator. Is calling it a DI Container a bad name? Is an IOC/DI Container just another type of Service Locator? Is the nuance in the fact that we use DI Containers mostly when we have many Dependencies?
发布评论
评论(16)
差异可能看起来很小,但即使使用 ServiceLocator,该类仍然负责创建其依赖项。它只是使用服务定位器来完成此操作。通过 DI,类被赋予了它的依赖关系。它既不知道,也不关心它们来自哪里。这样做的一个重要结果是 DI 示例更容易进行单元测试——因为您可以向它传递其依赖对象的模拟实现。如果您愿意,您可以将两者结合起来 - 并注入服务定位器(或工厂)。
The difference may seem slight, but even with the ServiceLocator, the class is still responsible for creating its dependencies. It just uses the service locator to do it. With DI, the class is given its dependencies. It neither knows, nor cares where they come from. One important result of this is that the DI example is much easier to unit test -- because you can pass it mock implementations of its dependent objects. You could combine the two -- and inject the service locator (or a factory), if you wanted.
当您使用服务定位器时,每个类都将依赖于您的服务定位器。依赖注入则不是这种情况。依赖注入器通常只会在启动时被调用一次,以将依赖项注入到某个主类中。该主类所依赖的类将递归地注入其依赖项,直到获得完整的对象图。
一个很好的比较:http://martinfowler.com/articles/injection.html
如果您的依赖注入器看起来像一个服务定位器,其中类直接调用注入器,它可能不是依赖注入器,而是一个服务定位器。
When you use a service locator, every class will have a dependency on your service locator. This is not the case with dependency injection. The dependency injector will typically be called only once at startup to inject dependencies into some main class. The classes this main class depends on will recursively have their dependencies injected, until you have a complete object graph.
A good comparison: http://martinfowler.com/articles/injection.html
If your dependency injector looks like a service locator, where the classes call the injector directly, it is probably not a dependency injector, but rather a service locator.
服务定位器隐藏依赖关系 - 您无法通过查看对象来判断它是否命中数据库(例如,当它从定位器获取连接时)。通过依赖注入(至少是构造函数注入),依赖关系是显式的。
此外,服务定位器打破了封装,因为它们提供了对其他对象的依赖关系的全局访问点。使用服务定位器,与任何单例一样:
通过依赖注入,一旦指定了对象的依赖关系,它们就受到对象本身的控制。
Service locators hide dependencies - you can't tell by looking at an object whether it hits a database or not (for example) when it obtains connections from a locator. With dependency injection (at least constructor injection) the dependencies are explicit.
Moreover, service locators break encapsulation because they provide a global point of access to dependencies of other objects. With service locator, as with any singleton:
With dependency injection, once an object's dependencies are specified, they are under control of the object itself.
马丁·福勒指出:
简而言之:服务定位器和依赖注入只是依赖倒置原则的实现。
重要的原则是“依赖于抽象,而不是依赖于具体”。这将使你的软件设计“松耦合”、“可扩展”、“灵活”。
您可以使用最适合您需求的一种。对于拥有庞大代码库的大型应用程序,您最好使用服务定位器,因为依赖注入需要对代码库进行更多更改。
您可以查看这篇文章: 依赖倒置:服务定位器或依赖注入
也是经典:Martin 的控制容器反转和依赖注入模式Fowler
设计可重用类作者:Ralph E. Johnson & Brian Foote
然而,让我大开眼界的是: ASP.NET MVC:解析还是注入?这就是问题所在……作者:Dino Esposito
Martin Fowler states:
In short: Service Locator and Dependency Injection are just implementations of Dependency Inversion Principle.
The important principle is “Depend upon Abstractions, not upon Concretions”. This will make your software design “loosely coupled”, “extensible”, “flexible”.
You can use the one that best fits your needs. For a big application, having a huge codebase, you'd better use a Service Locator, because Dependency Injection would require more changes to your codebase.
You can check this post: Dependency Inversion: Service Locator or Dependency Injection
Also the classic: Inversion of Control Containers and the Dependency Injection pattern by Martin Fowler
Designing Reusable Classes by Ralph E. Johnson & Brian Foote
However, the one that opened my eyes was: ASP.NET MVC: Resolve or Inject? That’s the Issue… by Dino Esposito
使用构造函数 DI 的类向使用代码表明存在需要满足的依赖关系。如果类在内部使用 SL 来检索此类依赖项,则使用代码不会意识到这些依赖项。这可能表面上看起来更好,但实际上了解任何显式依赖关系是有帮助的。从架构角度来看更好。在进行测试时,您必须知道某个类是否需要某些依赖项,并配置 SL 以提供这些依赖项的适当假版本。使用 DI,只需传递假货即可。虽然差别不大,但确实存在。
不过,DI 和 SL 可以一起工作。为常见依赖项(例如设置、记录器等)提供一个中心位置非常有用。给定一个使用此类 deps 的类,您可以创建一个接收 deps 的“真实”构造函数,以及一个从 SL 检索并转发到“真实”构造函数的默认(无参数)构造函数。
编辑:当然,当您使用 SL 时,您正在向该组件引入一些耦合。这很讽刺,因为这种功能的想法是鼓励抽象并减少耦合。这些担忧是可以平衡的,这取决于您需要在多少地方使用 SL。如果按照上面的建议完成,则只需在默认类构造函数中即可。
A class using constructor DI indicates to consuming code that there are dependencies to be satisfied. If the class uses the SL internally to retrieve such dependencies, the consuming code is not aware of the dependencies. This may on the surface seem better, but it is actually helpful to know of any explicit dependencies. It is better from an architectural view. And when doing testing, you have to know whether a class needs certain dependencies, and configure the SL to provide appropriate fake versions of those dependencies. With DI, just pass in the fakes. Not a huge difference, but it is there.
DI and SL can work together, though. It is useful to have a central location for common dependencies (e.g. settings, logger, etc). Given a class using such deps, you can create a "real" constructor that receives the deps, and a default (no parameter) constructor that retrieves from the SL and forwards to the "real" constructor.
EDIT: and, of course, when you use the SL, you are introducing some coupling to that component. Which is ironic, since the idea of such functionality is to encourage abstractions and reduce coupling. The concerns can be balanced, and it depends on how many places you would need to use the SL. If done as suggested above, just in the default class constructor.
两者都是IoC的实现技术。还有其他实现控制反转的模式:
(构造函数注入、参数注入(如果不需要)、接口注入的setter注入)
...
服务定位器和DI容器看起来更相似,它们都使用容器来定义依赖关系,将抽象映射到具体实现。
主要区别在于依赖关系的定位方式,在服务定位器中,客户端代码请求依赖关系,在 DI 容器中,我们使用容器来创建所有对象,并将依赖关系作为构造函数参数(或属性)注入。
Both of them are implementation techniques of IoC. There are also other patterns which implements Inversion of Control:
(constructor injection, parameter injection (if not required), setter injection of interface injection)
...
Service locator and DI Container seem more similar, both of them use a container to define dependencies, which maps abstraction to the concrete implementation.
The main difference is how the dependencies are located, in Service Locator, client code request the dependencies, in DI Container we use a container to create all of objects and it injects dependency as constructor parameters (or properties).
添加的一个原因是受到我们上周为 MEF 项目编写的文档更新的启发(我帮助构建 MEF)。
一旦应用程序由潜在的数千个组件组成,就很难确定是否可以正确实例化任何特定组件。我所说的“正确实例化”是指,在这个基于
Foo
组件的示例中,IBar
的实例将可用,并且提供它的组件将在您给出的第二个示例中,构造函数转到 IoC 容器来检索其依赖项,这是测试
Foo
实例能够正确实例化的唯一方法应用程序的实际运行时配置是实际构建它。这在测试时会产生各种尴尬的副作用,因为在运行时工作的代码不一定在测试工具下工作。模拟不会这样做,因为真正的配置是我们需要测试的东西,而不是一些测试时的设置。
这个问题的根源是 @Jon 已经指出的差异:通过构造函数注入依赖项是声明性的,而第二个版本使用命令式服务定位器模式。
如果仔细使用,IoC 容器可以静态分析应用程序的运行时配置,而无需实际创建所涉及组件的任何实例。许多流行的容器都提供了一些变体; Microsoft.Composition 是面向 .NET 4.5 Web 和 Metro 风格应用程序的 MEF 版本,在 wiki 文档中提供了
CompositionAssert
示例。使用它,您可以编写如下代码:(请参阅 this示例)。
通过在测试时验证应用程序的组合根,您可能会发现一些错误,否则这些错误可能会在稍后的过程中漏掉。
希望这是对该主题的这组全面答案的有趣补充!
One reason to add, inspired by a documentation update we wrote for the MEF project last week (I help build MEF).
Once an app is made up of potentially thousands of components, it can be difficult to determine whether any particular component can be instantiated correctly. By "instantiated correctly", I mean that in this example based on the
Foo
component, an instance ofIBar
and will be available, and that the component providing it will:In the second example you gave, where the constructor goes to the IoC container to retrieve its dependencies, the only way that you can test that an instance of
Foo
will be able to be instantiated correctly with the actual runtime configuration of your app is to actually construct it.This has all sorts of awkward side-effects at test time, because code that will work at runtime won't necessarily work under a test harness. Mocks won't do, because the real configuration is the thing we need to test, not some test-time setup.
The root of this problem is the difference already called out by @Jon: injecting dependencies through the constructor is declarative, while the second version uses the imperative Service Locator pattern.
An IoC container, when used carefully, can statically analyze the runtime configuration of your app without actually creating any instances of the components involved. Many popular containers provide some variation of this; Microsoft.Composition, which is the version of MEF targeting .NET 4.5 web and Metro style apps, provides a
CompositionAssert
sample in the wiki documentation. Using it, you can write code like:(See this example).
By verifying the Composition Roots of your application at test time you can potentially catch some errors that may otherwise slip through testing later in the process.
Hope this is an interesting addition to this otherwise comprehensive set of answers on the topic!
在我的上一个项目中,我同时使用了两者。
我使用依赖注入来实现单元可测试性。我使用服务定位器来隐藏实现并依赖于我的 IoC 容器。是的!一旦你使用了 IoC 容器(Unity、Ninject、Windsor Castle),你就会依赖它。一旦它过时或者由于某种原因您想要交换它,您将/可能需要更改您的实现 - 至少是组合根。但服务定位器抽象了该阶段。
你怎么能不依赖你的 IoC 容器呢?您要么需要自己包装它(这是一个坏主意),要么使用服务定位器来配置 IoC 容器。因此,您将告诉服务定位器获取您需要的接口,并且它将调用配置为检索该接口的 IoC 容器。
就我而言,我使用 ServiceLocator,这是一个框架组件。我将 Unity 用于我的 IoC 容器。如果将来我需要将 IoC 容器替换为 Ninject,我需要做的就是配置我的服务定位器以使用 Ninject 而不是 Unity。轻松迁移。
这是一篇很棒的文章,解释了这种情况;
http://www.johandekoning .nl/index.php/2013/03/03/dont-wrap-your-ioc-container/
In my last project I use both.
I use dependency injection for unit testability. I use service locator to hide implementation and being dependent to my IoC container. And YES! Once you use an IoC container (Unity, Ninject, Windsor Castle), you depend on it. And once it is outdated or for some reason you will want to swap it, you will/may need to change your implementation - at least the composition root. But service locator abstracts that phase.
How would you not depend on your IoC container? Either you will need to wrap it on your own (which is a bad idea), or you use a Service Locator to configure your IoC container. So you will tell the Service Locator to get the interface you need, and it will call the IoC container which is configured to retrieve that interface.
In my case, I use ServiceLocator, which is a framework component. And I use Unity for my IoC container. If in the future I need to swap my IoC container with Ninject, all I need to do is to configure my Service Locator to use Ninject instead of Unity. Easy migration.
Here is a great article that explains this scenario;
http://www.johandekoning.nl/index.php/2013/03/03/dont-wrap-your-ioc-container/
我认为两者一起工作。
依赖注入意味着您将一些依赖类/接口推送到消费类(通常是它的构造函数)。这通过接口将两个类解耦,并且意味着使用类可以使用多种类型的“注入依赖项”实现。
服务定位器的作用是将您的实施整合在一起。您可以在程序开始时通过一些引导程序设置服务定位器。引导是将一种实现类型与特定抽象/接口相关联的过程。它是在运行时为您创建的。 (基于您的配置或引导程序)。如果您没有实现依赖注入,那么使用服务定位器或 IOC 容器将非常困难。
I think the two work together.
Dependency injection means you push in some dependant class/interface to a consuming class (usually to it's constructor). This decouples the two classes via an interface and means the consuming class can work with many types of "injected dependency" implementations.
The role of the service locator is to pull together your implementation. You setup a service locator via some boot strapping at the start of your program. Bootstrapping is the process of associating a type of implementation to a particular abstract/interface. Which gets created for you at run time. (based on you config or bootstrap). If you hadn't implemented dependency injection, it would be very difficult to utilise a service locator or IOC container.
以下简单的概念让我更清楚地理解了 Service Locator 和 DI Container 之间的区别:
Service Locator 用于消费者,它通过 ID 从某些存储中拉取服务根据直接消费者的请求
DI容器 位于外部某个地方,它从某些存储中获取服务并将它们推送给消费者(无论是通过构造函数还是通过方法)
但是,我们只能在具体消费者使用的上下文中讨论它们之间的差异。当Service Locator和DI Container用于组合根时,它们几乎是相似的。
Following simple conception gave me a clearer understanding of difference between Service Locator and DI Container:
Service Locator is used in the consumer and it pulls services by ID from some storage by direct consumer's request
DI Container is located somewhere outside and it takes services from some storage and pushes them to the consumer (no matter via constructor or via method)
However, we can talk about difference between these only in context of concrete consumer usage. When Service Locator and DI Container are used in composition root, they are almost similar.
注意:我没有完全回答这个问题。但我觉得这对于依赖注入模式的新学习者来说很有用,他们对它与 服务定位器(反)模式 碰巧偶然发现此页面。
我知道服务定位器(现在似乎被视为反模式)和依赖注入模式之间的区别,并且可以理解每种模式的具体示例,但我对构造函数内显示服务定位器的示例感到困惑(假设我们'正在进行构造函数注入)。
“服务定位器”通常既用作模式的名称,又用作引用该模式中使用的对象(假设也是)的名称,以在不使用 new 运算符的情况下获取对象。现在,相同类型的对象也可以用在 组合根 执行依赖注入,这就是令人困惑的地方。
需要注意的是,您可能在 DI 构造函数内使用服务定位器对象,但您没有使用“服务定位器模式”。如果有人将其称为 IoC 容器对象,那么就不会那么令人困惑,因为您可能已经猜到它们本质上做了相同的事情(如果我错了,请纠正我)。
无论它是被称为服务定位器(或只是定位器),还是作为 IoC 容器(或只是容器),正如您所猜测的那样,都没有区别,它们可能指的是相同的抽象(如果我错了,请纠正我) )。只是将其称为服务定位器表明将服务定位器反模式与依赖注入模式一起使用。
恕我直言,将其命名为“定位器”而不是“位置”或“定位”,有时也会导致人们认为文章中的服务定位器指的是服务定位器容器,而不是服务定位器(反)模式,特别是当有一个称为依赖注入而不是依赖注入的相关模式时。
Note: I'm not exactly answering the question. But I feel that this can be useful for new learners of the Dependency Injection pattern who are confused about it with the Service Locator (anti-)pattern who happen to stumble onto this page.
I know the difference between the Service Locator (it's seems to be regarded as an anti-pattern now) and Dependency Injection patterns and can understand concrete examples each pattern, yet I was confused by examples showing a service locator inside the constructor (assume we're doing constructor injection).
"Service Locator" is often used both as the name of a pattern, and as the name to refer to the object (assume too) used in that pattern to obtain objects without using the new operator. Now, that same type of object can also be used at the composition root to perform dependency injection, and that's where the confusion comes in.
The point to note is that you may be using a service locator object inside a DI constructor, but you are not using the "Service Locator pattern". It is less confusing if one refers it as an IoC container object instead, as you may have guessed that they essentially do the same thing (do correct me if I'm wrong).
Whether it is referred to as a service locator (or just locator), or as an IoC container (or just container) makes no difference as you have guessed, they are probably referring to the same abstraction (do correct me if I'm wrong). It's just that calling it a service locator suggests that one is using the Service Locator anti-pattern together with the Dependency Injection pattern.
IMHO, naming it a 'locator' instead of 'location' or 'locating', can also cause one to sometimes think that the service locator in an article is referring to the Service Locator container, and not the Service Locator (anti-)pattern, especially when there's a related pattern called Dependency Injection and not Dependency Injector.
在这种过于简化的情况下,没有区别,它们可以互换使用。
然而,现实世界的问题并不那么简单。假设 Bar 类本身有另一个名为 D 的依赖项。在这种情况下,您的服务定位器将无法解析该依赖项,您必须在 D 类中实例化它;因为实例化它们的依赖关系是类的责任。如果 D 类本身有其他依赖项,情况会变得更糟,并且在现实情况下,它通常会变得比这更复杂。在这种情况下,DI 是比 ServiceLocator 更好的解决方案。
In this oversimplified case there is no difference and they can be used interchangeably.
However, real world problems are not as simple. Just assume that the Bar class itself had another dependency named D. In that case your service locator wouldn't be able to resolve that dependency and you would have to instantiate it within the D class; because it is the responsibility of your classes to instantiate their dependencies. It would even get worse if the D class itself had other dependencies and in real-world situations it usually gets even more complicated than that. In such scenarios DI is a better solution than ServiceLocator.
服务定位器和依赖注入都是遵循依赖倒置原则的对象访问模式实现。
依赖注入是[静态/全局]对象访问模式
服务定位器是[动态]对象访问模式
如果您需要处理[动态结构],例如[ui树]或任何[分形设计的应用程序],您可能需要服务定位器。
示例:
如果您只想从类中获取一个实例,而不关心应用程序的层次结构和位置对于该层次结构中的实例,您应该使用 DI。
示例:
当您在运行前不知道服务的实际提供者时,可以使用
。当您知道静态容器提供该服务时,就会使用 DI。
服务定位器模式更像是模块级别依赖提供者,而DI是全局级别。
当有一个子模块声明应由其父模块提供的服务的依赖关系而不是静态解析类型(singleton/transient/静态范围)。
它可以通过 DI 的范围注入模式来实现,而范围是由应用程序的模块结构/关系定义的。
个人建议:
详细信息:https://learn.microsoft .com/zh-cn/dotnet/core/extensions/dependency-injection-guidelines#recommendations
Service Locator and Dependency Injection are both Object Access Pattern implementation that obey the Dependency Inversion Principle
Dependency Injection are [static/global] object access pattern
Service Locator are [dynamic] object access pattern
If you need to handle [dynamic structure] like [ui tree] or any [fractal designed application], you may need Service Locator.
Example:
If you only want to get a instance from your class that don't care about the hierarchy of the application and the position of the instance in that hierarchy, you should use DI.
Example:
Service Locator is used when you don't know the actual provider of the service before runtime.
DI is used when you know it's the static container that provides that service.
Service Locator pattern is more like a module level Dependency Providers, while DI is global level.
It's very useful when there is a sub-module that declare dependency of a service that should be provided by it's parent-module instead of a static resolve type(singleton/transient/static-scoped).
It can be implemented by a scoped injection pattern of DI while the scope is defined by the module structure/relation of the application.
Personal suggestion:
Detailed information: https://learn.microsoft.com/zh-cn/dotnet/core/extensions/dependency-injection-guidelines#recommendations
依赖注入和服务定位器之间有什么区别(如果有)?两种模式都擅长实现依赖倒置原则。服务定位器模式在现有代码库中更易于使用,因为它使整体设计更加宽松,而无需强制更改公共接口。出于同样的原因,基于服务定位器模式的代码的可读性低于基于依赖项注入的等效代码。
依赖注入模式清楚地表明了类(或方法)将具有哪些依赖项的签名。因此,生成的代码更加干净且更具可读性。
What’s the difference (if any) between Dependency Injection and Service Locator? Both patterns are good at implementing the Dependency Inversion principle. The Service Locator pattern is easier to use in an existing codebase as it makes the overall design looser without forcing changes to the public interface. For this same reason, code that is based on the Service Locator pattern is less readable than equivalent code that is based on Dependency Injection.
The Dependency Injection pattern makes it clear since the signature which dependencies a class (or a method) is going to have. For this reason, the resulting code is cleaner and more readable.
DI 容器是服务定位器的超集。它可用于定位服务,并具有组装(连接)依赖项注入的附加功能。
DI container is a superset of service locator. It can be used to locate a service, with additional capability of assembling (wiring) the injections of dependency.
郑重声明
除非您确实需要一个接口(该接口由多个类使用),否则您不得使用它。在这种情况下,IBar 允许使用任何实现它的服务类。但是,通常,该接口将由单个类使用。
为什么使用接口是一个坏主意?因为调试起来确实很困难。
例如,假设实例“bar”失败,问题:哪个类失败? 我应该修复哪些代码?一个简单的视图,它会导致一个接口,我的路就到此为止了。
相反,如果代码使用硬依赖项,则很容易调试错误。
如果“bar”失败,那么我应该检查并启动 BarService 类。
For the record
Unless you really need an interface (the interface is used by more than one class), you MUST NOT USE IT. In this case, IBar allows utilizing any service class, that implements it. However, usually, this Interface will be used by a single class.
Why it is a bad idea to use an interface?. Because it is really hard to debug.
For example, let's say that the instance "bar" failed, question: which class failed?. Which code I should fix? A simple view, it leads to an Interface, and it's here where my road ends.
Instead, if the code uses a hard dependency then it's easy to debug a mistake.
If "bar" fails, then I should check and fir the class BarService.