本文翻译自:Why does one use dependency injection?
I'm trying to understand dependency injections (DI), and once again I failed. 我正在尝试理解依赖注射 (DI),并再一次失败了。 It just seems silly. 这看起来很傻。 My code is never a mess; 我的代码从来都不是一团糟; I hardly write virtual functions and interfaces (although I do once in a blue moon) and all my configuration is magically serialized into a class using json.net (sometimes using an XML serializer). 我几乎没有编写虚函数和接口(虽然我曾经在蓝月亮中做过)并且我的所有配置都被神奇地序列化为使用json.net的类(有时使用XML序列化器)。
I don't quite understand what problem it solves. 我不太明白它解决了什么问题。 It looks like a way to say: "hi. When you run into this function, return an object that is of this type and uses these parameters/data." 它看起来像是一种说法:“嗨。当你遇到这个函数时,返回一个这种类型的对象并使用这些参数/数据。”
But... why would I ever use that? 但是......为什么我会用它呢? Note I have never needed to use object
as well, but I understand what that is for. 注意我从来不需要使用object
,但我明白这是什么。
What are some real situations in either building a website or desktop application where one would use DI? 在构建网站或桌面应用程序时,哪些人会使用DI? I can come up with cases easily for why someone may want to use interfaces/virtual functions in a game, but it's extremely rare (rare enough that I can't remember a single instance) to use that in non-game code. 我可以轻松地提出案例,为什么有人可能想在游戏中使用接口/虚拟功能,但在非游戏代码中使用它非常罕见(很少见,我记不起单个实例)。
参考:https://stackoom.com/question/y0RZ/为什么使用依赖注入
First, I want to explain an assumption that I make for this answer. 首先,我想解释一下我为这个答案做出的假设。 It is not always true, but quite often: 它并不总是如此,但经常是:
Interfaces are adjectives; 界面是形容词; classes are nouns. 课程是名词。
(Actually, there are interfaces that are nouns as well, but I want to generalize here.) (实际上,有些名词也是名词,但我想在这里概括一下。)
So, eg an interface may be something such as IDisposable
, IEnumerable
or IPrintable
. 因此,例如,接口可以是诸如IDisposable
, IEnumerable
或IPrintable
。 A class is an actual implementation of one or more of these interfaces: List
or Map
may both be implementations of IEnumerable
. 类是这些接口中的一个或多个的实际实现: List
或Map
都可以是IEnumerable
实现。
To get the point: Often your classes depend on each other. 要明白这一点:通常你的课程相互依赖。 Eg you could have a Database
class which accesses your database (hah, surprise! ;-)), but you also want this class to do logging about accessing the database. 例如,您可以拥有一个访问数据库的Database
类(hah,surprise!;-)),但您也希望此类执行有关访问数据库的日志记录。 Suppose you have another class Logger
, then Database
has a dependency to Logger
. 假设您有另一个类Logger
,那么Database
与Logger
有依赖关系。
So far, so good. 到现在为止还挺好。
You can model this dependency inside your Database
class with the following line: 您可以使用以下行在Database
类中对此依赖项进行建模:
var logger = new Logger();
and everything is fine. 一切都很好。 It is fine up to the day when you realize that you need a bunch of loggers: Sometimes you want to log to the console, sometimes to the file system, sometimes using TCP/IP and a remote logging server, and so on ... 当你意识到你需要一堆记录器时,这是很好的:有时你想要登录到控制台,有时你想登录到文件系统,有时候使用TCP / IP和远程登录服务器,等等......
And of course you do NOT want to change all your code (meanwhile you have gazillions of it) and replace all lines 当然,你不想改变所有的代码(同时你拥有它gazillions)和替换所有行
var logger = new Logger();
by: 通过:
var logger = new TcpLogger();
First, this is no fun. 首先,这不好玩。 Second, this is error-prone. 其次,这容易出错。 Third, this is stupid, repetitive work for a trained monkey. 第三,对于训练有素的猴子来说,这是一项愚蠢的,重复性的工作。 So what do you do? 所以你会怎么做?
Obviously it's a quite good idea to introduce an interface ICanLog
(or similar) that is implemented by all the various loggers. 显然,引入由所有各种记录器实现的接口ICanLog
(或类似物)是一个相当不错的主意。 So step 1 in your code is that you do: 因此,代码中的第1步是:
ICanLog logger = new Logger();
Now the type inference doesn't change type any more, you always have one single interface to develop against. 现在类型推断不再改变类型,你总是有一个单独的接口来开发。 The next step is that you do not want to have new Logger()
over and over again. 下一步是您不希望一遍又一遍地使用new Logger()
。 So you put the reliability to create new instances to a single, central factory class, and you get code such as: 因此,您可以为单个中央工厂类创建新实例,并获得以下代码:
ICanLog logger = LoggerFactory.Create();
The factory itself decides what kind of logger to create. 工厂本身决定要创建哪种记录器。 Your code doesn't care any longer, and if you want to change the type of logger being used, you change it once : Inside the factory. 您的代码不再关心,如果您想更改正在使用的记录器类型,您可以更改一次 :在工厂内部。
Now, of course, you can generalize this factory, and make it work for any type: 当然,现在您可以概括这个工厂,并使其适用于任何类型:
ICanLog logger = TypeFactory.Create();
Somewhere this TypeFactory needs configuration data which actual class to instantiate when a specific interface type is requested, so you need a mapping. 在某个地方,这个TypeFactory需要配置数据,当请求特定的接口类型时,实际的类要实例化,所以你需要一个映射。 Of course you can do this mapping inside your code, but then a type change means recompiling. 当然,您可以在代码中进行此映射,但是类型更改意味着重新编译。 But you could also put this mapping inside an XML file, eg. 但您也可以将此映射放在XML文件中,例如。 This allows you to change the actually used class even after compile time (!), that means dynamically, without recompiling! 这允许您甚至在编译时(!)之后更改实际使用的类,这意味着动态地,无需重新编译!
To give you a useful example for this: Think of a software that does not log normally, but when your customer calls and asks for help because he has a problem, all you send to him is an updated XML config file, and now he has logging enabled, and your support can use the log files to help your customer. 为您提供一个有用的示例:想想一个不能正常登录的软件,但是当您的客户打电话并因为遇到问题而请求帮助时,您发送给他的只是一个更新的XML配置文件,现在他已经已启用日志记录,您的支持人员可以使用日志文件来帮助您的客户。
And now, when you replace names a little bit, you end up with a simple implementation of a Service Locator , which is one of two patterns for Inversion of Control (since you invert control over who decides what exact class to instantiate). 现在,当您稍微更换名称时,最终会得到一个服务定位器的简单实现,这是控制反转的两种模式之一 (因为您可以控制谁决定要实例化的确切类)。
All in all this reduces dependencies in your code, but now all your code has a dependency to the central, single service locator. 总而言之,这减少了代码中的依赖关系,但现在所有代码都依赖于中央单一服务定位器。
Dependency injection is now the next step in this line: Just get rid of this single dependency to the service locator: Instead of various classes asking the service locator for an implementation for a specific interface, you - once again - revert control over who instantiates what. 依赖注入现在是这一行的下一步:只需要去掉服务定位器的这个单一依赖:代替各种类询问服务定位器是否有特定接口的实现,你再一次 - 恢复对谁实例化什么的控制。
With dependency injection, your Database
class now has a constructor that requires a parameter of type ICanLog
: 通过依赖注入,您的Database
类现在有一个构造函数,它需要一个ICanLog
类型的参数:
public Database(ICanLog logger) { ... }
Now your database always has a logger to use, but it does not know any more where this logger comes from. 现在你的数据库总是有一个记录器可供使用,但它不知道这个记录器的来源。
And this is where a DI framework comes into play: You configure your mappings once again, and then ask your DI framework to instantiate your application for you. 这就是DI框架发挥作用的地方:您再次配置映射,然后让您的DI框架为您实例化您的应用程序。 As the Application
class requires an ICanPersistData
implementation, an instance of Database
is injected - but for that it must first create an instance of the kind of logger which is configured for ICanLog
. 由于Application
类需要ICanPersistData
实现,因此会注入一个Database
实例 - 但为此必须先创建一个为ICanLog
配置的logger实例。 And so on ... 等等 ...
So, to cut a long story short: Dependency injection is one of two ways of how to remove dependencies in your code. 因此,简而言之:依赖注入是如何在代码中删除依赖关系的两种方法之一。 It is very useful for configuration changes after compile-time, and it is a great thing for unit testing (as it makes it very easy to inject stubs and / or mocks). 它对于编译后的配置更改非常有用,对于单元测试来说它是一件好事(因为它可以很容易地注入存根和/或模拟)。
In practice, there are things you can not do without a service locator (eg, if you do not know in advance how many instances you do need of a specific interface: A DI framework always injects only one instance per parameter, but you can call a service locator inside a loop, of course), hence most often each DI framework also provides a service locator. 在实践中,有些事情没有服务定位器就无法做到(例如,如果你事先不知道你需要多少个特定接口的实例:一个DI框架总是每个参数只注入一个实例,但你可以调用当然,循环内的服务定位器),因此大多数情况下每个DI框架也提供服务定位器。
But basically, that's it. 但基本上就是这样。
Hope that helps. 希望有所帮助。
PS: What I described here is a technique called constructor injection , there is also property injection where not constructor parameters, but properties are being used for defining and resolving dependencies. PS:我在这里描述的是一种称为构造函数注入的技术,还有属性注入 ,其中没有构造函数参数,但属性用于定义和解析依赖项。 Think of property injection as an optional dependency, and of constructor injection as mandatory dependencies. 将属性注入视为可选依赖项,将构造函数注入视为必需依赖项。 But discussion on this is beyond the scope of this question. 但对此的讨论超出了这个问题的范围。
I think a lot of times people get confused about the difference between dependency injection and a dependency injection framework (or a container as it is often called). 我想很多时候人们对依赖注入和依赖注入框架 (或者通常称为容器 )之间的区别感到困惑。
Dependency injection is a very simple concept. 依赖注入是一个非常简单的概念。 Instead of this code: 而不是这段代码:
public class A {
private B b;
public A() {
this.b = new B(); // A *depends on* B
}
public void DoSomeStuff() {
// Do something with B here
}
}
public static void Main(string[] args) {
A a = new A();
a.DoSomeStuff();
}
you write code like this: 你写这样的代码:
public class A {
private B b;
public A(B b) { // A now takes its dependencies as arguments
this.b = b; // look ma, no "new"!
}
public void DoSomeStuff() {
// Do something with B here
}
}
public static void Main(string[] args) {
B b = new B(); // B is constructed here instead
A a = new A(b);
a.DoSomeStuff();
}
And that's it. 就是这样。 Seriously. 认真。 This gives you a ton of advantages. 这给你带来了很多好处。 Two important ones are the ability to control functionality from a central place (the Main()
function) instead of spreading it throughout your program, and the ability to more easily test each class in isolation (because you can pass mocks or other faked objects into its constructor instead of a real value). 两个重要的是能够从中心位置( Main()
函数)控制功能,而不是在整个程序中传播它,并且能够更容易地隔离测试每个类(因为你可以将模拟或其他伪造的对象传递到它的构造函数而不是实际值)。
The drawback, of course, is that you now have one mega-function that knows about all the classes used by your program. 当然,缺点是你现在有一个知道你的程序使用的所有类的超级函数。 That's what DI frameworks can help with. 这就是DI框架可以提供的帮助。 But if you're having trouble understanding why this approach is valuable, I'd recommend starting with manual dependency injection first, so you can better appreciate what the various frameworks out there can do for you. 但如果您无法理解为什么这种方法很有价值,我建议首先从手动依赖注入开始,这样您就可以更好地了解那里的各种框架可以为您做些什么。
The main reason to use DI is that you want to put the responsibility of the knowledge of the implementation where the knowledge is there. 使用DI的主要原因是您希望将实施知识的责任放在知识所在的位置。 The idea of DI is very much inline with encapsulation and design by interface. DI的概念非常符合界面封装和设计。 If the front end asks from the back end for some data, then is it unimportant for the front end how the back end resolves that question. 如果前端从后端询问某些数据,那么前端后端如何解决该问题并不重要。 That is up to the requesthandler. 这取决于requesthandler。
That is already common in OOP for a long time. 这在OOP中已经很常见了很长时间。 Many times creating code pieces like: 很多时候创建代码片段如:
I_Dosomething x = new Impl_Dosomething();
The drawback is that the implementation class is still hardcoded, hence has the front end the knowledge which implementation is used. 缺点是实现类仍然是硬编码的,因此前端具有使用实现的知识。 DI takes the design by interface one step further, that the only thing the front end needs to know is the knowledge of the interface. DI通过接口进一步采用设计,前端唯一需要知道的是接口的知识。 In between the DYI and DI is the pattern of a service locator, because the front end has to provide a key (present in the registry of the service locator) to lets its request become resolved. 在DYI和DI之间是服务定位器的模式,因为前端必须提供密钥(存在于服务定位器的注册表中)以使其请求得到解决。 Service locator example: 服务定位器示例:
I_Dosomething x = ServiceLocator.returnDoing(String pKey);
DI example: DI例子:
I_Dosomething x = DIContainer.returnThat();
One of the requirements of DI is that the container must be able to find out which class is the implementation of which interface. DI的一个要求是容器必须能够找出哪个类是哪个接口的实现。 Hence does a DI container require strongly typed design and only one implementation for each interface at the same time. 因此,DI容器需要强类型设计,并且每个接口同时只需要一个实现。 If you need more implementations of an interface at the same time (like a calculator), you need the service locator or factory design pattern. 如果您需要同时实现更多接口(如计算器),则需要服务定位器或工厂设计模式。
D(b)I: Dependency Injection and Design by Interface. D(b)I:依赖注入和接口设计。 This restriction is not a very big practical problem though. 这种限制虽然不是一个很大的实际问题。 The benefit of using D(b)I is that it serves communication between the client and the provider. 使用D(b)I的好处是它服务于客户端和提供者之间的通信。 An interface is a perspective on an object or a set of behaviours. 界面是对象或一组行为的透视图。 The latter is crucial here. 后者在这里至关重要。
I prefer the administration of service contracts together with D(b)I in coding. 我更喜欢在编码时与D(b)I一起管理服务合同。 They should go together. 他们应该一起去。 The use of D(b)I as a technical solution without organizational administration of service contracts is not very beneficial in my point of view, because DI is then just an extra layer of encapsulation. 在我的观点中,使用D(b)I作为技术解决方案而没有组织管理服务合同并不是非常有益,因为DI只是一个额外的封装层。 But when you can use it together with organizational administration you can really make use of the organizing principle D(b)I offers. 但是当你可以将它与组织管理一起使用时,你可以真正地利用我提供的组织原则D(b)。 It can help you in the long run to structure communication with the client and other technical departments in topics as testing, versioning and the development of alternatives. 从长远来看,它可以帮助您与客户和其他技术部门建立联系,包括测试,版本控制和替代方案的开发。 When you have an implicit interface as in a hardcoded class, then is it much less communicable over time then when you make it explicit using D(b)I. 当你在硬编码类中有一个隐式接口时,那么随着时间的推移,当你使用D(b)I将其显式化时,它的可通信性要小得多。 It all boils down to maintenance, which is over time and not at a time. 这一切都归结为维护,这是随着时间的推移,而不是一次。 :-) :-)
As the other answers stated, dependency injection is a way to create your dependencies outside of the class that uses it. 正如其他答案所述,依赖注入是一种在使用它的类之外创建依赖项的方法。 You inject them from the outside, and take control about their creation away from the inside of your class. 你从外面注入它们,并从你的班级内部控制他们的创造。 This is also why dependency injection is a realization of the Inversion of control (IoC) principle. 这也是依赖注入是控制反转 (IoC)原理的实现的原因。
IoC is the principle, where DI is the pattern. IoC是原则,其中DI是模式。 The reason that you might "need more than one logger" is never actually met, as far as my experience goes, but the actualy reason is, that you really need it, whenever you test something. 就我的经验而言,你可能“需要多个记录器”的原因从未真正得到满足,但实际的原因是,无论何时你测试某些东西,你真的需要它。 An example: 一个例子:
My Feature: 我的特点:
When I look at an offer, I want to mark that I looked at it automatically, so that I don't forget to do so. 当我看到报价时,我想标记我自动查看它,以便我不会忘记这样做。
You might test this like this: 您可以像这样测试:
[Test]
public void ShouldUpdateTimeStamp
{
// Arrange
var formdata = { . . . }
// System under Test
var weasel = new OfferWeasel();
// Act
var offer = weasel.Create(formdata)
// Assert
offer.LastUpdated.Should().Be(new DateTime(2013,01,13,13,01,0,0));
}
So somewhere in the OfferWeasel
, it builds you an offer Object like this: 所以在OfferWeasel
某个地方,它会为你构建一个这样的商品对象:
public class OfferWeasel
{
public Offer Create(Formdata formdata)
{
var offer = new Offer();
offer.LastUpdated = DateTime.Now;
return offer;
}
}
The problem here is, that this test will most likely always fail, since the date that is being set will differ from the date being asserted, even if you just put DateTime.Now
in the test code it might be off by a couple of milliseconds and will therefore always fail. 这里的问题是,这个测试很可能总是失败,因为正在设置的日期将与被声明的日期不同,即使你只是将DateTime.Now
放在测试代码中它可能会被关闭几毫秒因此总会失败。 A better solution now would be to create an interface for this, that allows you to control what time will be set: 现在更好的解决方案是为此创建一个接口,允许您控制将设置的时间:
public interface IGotTheTime
{
DateTime Now {get;}
}
public class CannedTime : IGotTheTime
{
public DateTime Now {get; set;}
}
public class ActualTime : IGotTheTime
{
public DateTime Now {get { return DateTime.Now; }}
}
public class OfferWeasel
{
private readonly IGotTheTime _time;
public OfferWeasel(IGotTheTime time)
{
_time = time;
}
public Offer Create(Formdata formdata)
{
var offer = new Offer();
offer.LastUpdated = _time.Now;
return offer;
}
}
The Interface is the abstraction. 接口是抽象。 One is the REAL thing, and the other one allows you to fake some time where it is needed. 一个是真实的东西,另一个允许你假装需要它的时间。 The test can then be changed like this: 然后可以像这样更改测试:
[Test]
public void ShouldUpdateTimeStamp
{
// Arrange
var date = new DateTime(2013, 01, 13, 13, 01, 0, 0);
var formdata = { . . . }
var time = new CannedTime { Now = date };
// System under test
var weasel= new OfferWeasel(time);
// Act
var offer = weasel.Create(formdata)
// Assert
offer.LastUpdated.Should().Be(date);
}
Like this, you applied the "inversion of control" principle, by injecting a dependency (getting the current time). 像这样,你通过注入一个依赖(获取当前时间)来应用“控制反转”原理。 The main reason to do this is for easier isolated unit testing, there are other ways of doing it. 这样做的主要原因是为了更容易进行隔离单元测试,还有其他方法可以做到这一点。 For example, an interface and a class here is unnecessary since in C# functions can be passed around as variables, so instead of an interface you could use a Func
to achieve the same. 例如,这里的接口和类是不必要的,因为在C#函数中可以作为变量传递,因此您可以使用Func
来实现相同的接口而不是接口。 Or, if you take a dynamic approach, you just pass any object that has the equivalent method ( duck typing ), and you don't need an interface at all. 或者,如果采用动态方法,则只传递具有等效方法的任何对象( 鸭子类型 ),并且根本不需要接口。
You will hardly ever need more than one logger. 您几乎不需要多个记录器。 Nonetheless, dependency injection is essential for statically typed code such as Java or C#. 尽管如此,依赖注入对于静态类型代码(如Java或C#)至关重要。
And... It should also be noted that an object can only properly fulfill its purpose at runtime, if all its dependencies are available, so there is not much use in setting up property injection. 并且......还应该注意,如果一个对象的所有依赖项都可用,那么它只能在运行时正确地实现其目的,因此在设置属性注入时没有多大用处。 In my opinion, all dependencies should be satisfied when the constructor is being called, so constructor-injection is the thing to go with. 在我看来,在调用构造函数时应该满足所有依赖项,因此构造函数注入是最佳选择。
I hope that helped. 我希望有所帮助。
I think the classic answer is to create a more decoupled application, which has no knowledge of which implementation will be used during runtime. 我认为经典的答案是创建一个更加分离的应用程序,它不知道在运行时将使用哪个实现。
For example, we're a central payment provider, working with many payment providers around the world. 例如,我们是一家中央支付提供商,与世界各地的许多支付提供商合作。 However, when a request is made, I have no idea which payment processor I'm going to call. 但是,当提出请求时,我不知道我要打电话给哪个支付处理器。 I could program one class with a ton of switch cases, such as: 我可以使用大量的开关案例编写一个类,例如:
class PaymentProcessor{
private String type;
public PaymentProcessor(String type){
this.type = type;
}
public void authorize(){
if (type.equals(Consts.PAYPAL)){
// Do this;
}
else if(type.equals(Consts.OTHER_PROCESSOR)){
// Do that;
}
}
}
Now imagine that now you'll need to maintain all this code in a single class because it's not decoupled properly, you can imagine that for every new processor you'll support, you'll need to create a new if // switch case for every method, this only gets more complicated, however, by using Dependency Injection (or Inversion of Control - as it's sometimes called, meaning that whoever controls the running of the program is known only at runtime, and not complication), you could achieve something very neat and maintainable. 现在想象一下,现在你需要在一个类中维护所有这些代码,因为它没有正确解耦,你可以想象,对于你支持的每个新处理器,你需要创建一个新的if // switch案例每个方法,这只会变得更复杂,但是,通过使用依赖注入(或控制反转 - 因为它有时被称为,意味着无论谁控制程序的运行只在运行时知道,而不是复杂),你可以实现一些东西非常整洁和可维护。
class PaypalProcessor implements PaymentProcessor{
public void authorize(){
// Do PayPal authorization
}
}
class OtherProcessor implements PaymentProcessor{
public void authorize(){
// Do other processor authorization
}
}
class PaymentFactory{
public static PaymentProcessor create(String type){
switch(type){
case Consts.PAYPAL;
return new PaypalProcessor();
case Consts.OTHER_PROCESSOR;
return new OtherProcessor();
}
}
}
interface PaymentProcessor{
void authorize();
}
** The code won't compile, I know :) **代码不会编译,我知道:)