首先,我想解释一个我对此答案所作的假设。这并不总是正确的,但是经常:
接口是形容词;类是名词。
(实际上,也有一些接口也是名词,但是我想在这里概括一下。)
因此,例如,一个接口可以是这样的东西IDisposable
,IEnumerable
或IPrintable
。类是这些接口中一个或多个接口的实际实现:List
或者Map
都可以是的实现IEnumerable
。
明白这一点:通常,您的类相互依赖。例如,您可能有一个Database
访问数据库的类(哈哈,惊喜!;-)),但是您还希望该类记录有关访问数据库的日志。假设您还有另一个类Logger
,然后Database
对进行依赖Logger
。
到目前为止,一切都很好。
您可以Database
使用以下代码行在类中对该依赖关系进行建模:
var logger = new Logger();
一切都很好。当您意识到自己需要一堆记录器的时候就很好了:有时您想登录到控制台,有时要登录到文件系统,有时要使用TCP / IP和远程日志记录服务器,等等。
当然,你也不要想改变所有的代码(同时你拥有它gazillions)和替换所有行
var logger = new Logger();
通过:
var logger = new TcpLogger();
首先,这没什么好玩的。其次,这容易出错。第三,对于受过训练的猴子来说,这是愚蠢的重复性工作。所以你会怎么做?
显然,引入一个ICanLog
由所有各种记录器实现的接口(或类似接口)是一个很好的主意。因此,代码中的第1步是您要做的:
ICanLog logger = new Logger();
现在,类型推断不再更改类型,您始终只有一个要开发的接口。下一步是您不想new Logger()
一遍又一遍。因此,您可以将创建新实例的可靠性放在一个单一的中央工厂类中,并获得如下代码:
ICanLog logger = LoggerFactory.Create();
工厂自己决定要创建哪种记录器。您的代码不再需要关心,如果您想更改所使用的记录器的类型,则只需在工厂内部进行一次更改。
现在,当然,您可以概括该工厂,并使其适用于任何类型:
ICanLog logger = TypeFactory.Create<ICanLog>();
此TypeFactory在某个地方需要配置数据,以便在请求特定接口类型时实例化哪个实际类,因此您需要一个映射。当然,您可以在代码内部进行此映射,但是类型更改意味着重新编译。但是您也可以将此映射放入XML文件中,例如。这样,即使在编译时间(!)之后,您也可以更改实际使用的类,这意味着可以动态地进行更改,而无需重新编译!
为您提供一个有用的示例:考虑一种无法正常登录的软件,但是当您的客户由于问题而致电并寻求帮助时,您发送给他的只是一个更新的XML配置文件,现在他拥有已启用日志记录,并且您的支持人员可以使用日志文件来帮助您的客户。
现在,当您稍微替换名称时,最终得到Service Locator的简单实现,这是控制反转的两种模式之一(因为您可以反转控制权,由谁来决定要实例化的确切类)。
总而言之,这减少了代码中的依赖性,但是现在所有代码都具有对中央单一服务定位器的依赖性。
现在,依赖注入是该行的下一步:摆脱对服务定位器的单一依赖关系:代替各种类向服务定位器要求特定接口的实现,您-再次-还原对实例化对象的控制。 。
通过依赖注入,您的Database
类现在有了一个构造函数,该构造函数需要一个type类型的参数ICanLog
:
public Database(ICanLog logger) { ... }
现在,您的数据库中始终有一个记录器可供使用,但不再知道该记录器来自何处。
这就是DI框架起作用的地方:您再次配置映射,然后要求DI框架为您实例化您的应用程序。由于Application
该类需要ICanPersistData
实现,因此将插入的实例Database
-但为此必须首先创建配置为的记录器类型的实例ICanLog
。等等 ...
因此,简而言之:依赖关系注入是在代码中删除依赖关系的两种方法之一。它对于编译后的配置更改非常有用,并且对于单元测试来说是一件好事(因为这使得注入存根和/或模拟非常容易)。
实际上,有些事情没有服务定位器是无法做的(例如,如果您事先不知道特定接口需要多少个实例:DI框架始终每个参数仅注入一个实例,但是您可以调用当然,在循环内包含一个服务定位器),因此,每个DI框架通常都提供一个服务定位器。
但基本上就是这样。
PS:我在这里介绍的是一种称为构造函数注入的技术,还有一种属性注入,其中不是构造函数参数,而是使用属性来定义和解析依赖项。将属性注入视为可选依赖项,将构造函数注入视为强制性依赖项。但是,对此的讨论超出了此问题的范围。