[英]Inversion of Control vs Dependency Injection
根据Martin Fowler 撰写的论文,控制反转是程序控制流反转的原理:不是程序员控制程序流,而是外部源(框架、服务、其他组件)控制它。 就像我们将某些东西插入到其他东西中一样。 他提到了一个关于 EJB 2.0 的例子:
例如,Session Bean 接口定义了 ejbRemove、ejbPassivate(存储到辅助存储)和 ejbActivate(从被动状态恢复)。 您无法控制何时调用这些方法,而只能控制它们的作用。 容器呼叫我们,我们不呼叫它。
这导致了框架和库之间的区别:
控制反转是使框架与库不同的关键部分。 库本质上是一组可以调用的函数,现在通常组织成类。 每个调用都会做一些工作并将控制权返回给客户端。
我认为,DI 是 IOC 的观点,意味着 object 的依赖关系是倒置的:它不是控制自己的依赖关系,而是生命周期......还有其他东西为你做。 但是,正如你亲手告诉我的 DI,DI 不一定是 IOC。 我们仍然可以有 DI 而没有 IOC。
但是,在这篇论文中(来自 pococapsule,另一个 C/C++ 的 IOC 框架),它表明由于 IOC 和 DI,IOC 容器和 DI 框架远优于 J2EE,因为 J2EE 将框架代码混合到组件中,因此不会使其成为普通的旧 Java/C++ Object (POJO/POCO)。
依赖注入模式以外的控制容器的反转(存档链接)
附加阅读以了解旧的基于组件的开发框架有什么问题,这导致了上面的第二篇论文: 控制反转的原因和内容(存档链接)
我的问题:IOC 和 DI 到底是什么? 我很困惑。 基于 pococapsule,IOC 不仅仅是对象或程序员与框架之间的控制反转更重要的东西。
Inversion-of-Control
(IoC)模式是关于提供任何类型的callback
(“实现”和/或控制反应),而不是直接行动(换句话说,反转和/或将控制重定向到外部处理程序/控制器)。
例如,框架调用应用程序提供的实现,而不是让应用程序调用库(也称为工具包)提供的实现。
Dependency-Injection
(DI)模式是 IoC 模式的更具体版本,其中实现通过构造函数/设置器/服务查找传递到 object,object 将“依赖”以正确运行。
每个
DI
实现都可以被视为IoC
,但不应将其称为IoC
,因为实现依赖注入比回调更难(不要使用通用术语“IoC”来降低产品的价值)。
例如,不使用 DI 的 IoC将是模板模式,因为只能通过子类化来更改实现。
DI 框架旨在利用 DI,并且可以定义接口(或 Java 中的 Annotations)以使其易于传递实现。
IoC 容器是可以在编程语言之外工作的 DI 框架。 在某些情况下,您可以配置在侵入性较小的元数据文件(例如 XML)中使用哪些实现。 有了一些,您可以执行通常不可能的 IoC,例如在pointcuts处注入实现。
另请参阅此Martin Fowler 的文章。
简而言之,IoC 是一个更广泛的术语,包括但不限于 DI
控制反转 (IoC) 一词最初是指整体框架或运行时控制程序流的任何类型的编程风格
在 DI 有名字之前,人们开始将管理依赖的框架称为 Inversion of Control Containers,很快,IoC 的含义逐渐转向了这个特定的含义:Inversion of Control over Dependencies。
控制反转(IoC) 意味着对象不会创建它们赖以完成工作的其他对象。 相反,他们从外部来源(例如,xml 配置文件)获取他们需要的对象。
依赖注入(DI) 意味着这是在没有 object 干预的情况下完成的,通常由传递构造函数参数和设置属性的框架组件完成。
IoC ( C控制的反转):- 这是一个通用术语,以多种方式实现(事件、代表等)。
DI (依赖注入):- DI 是IoC的一个子类型,通过构造函数注入、setter 注入或接口注入来实现。
但是,Spring 仅支持以下两种类型:
NullPointerException: bean does not exist
。 构造函数注入是注入依赖项的最佳实践。DI 是 IoC 的子集
IOC(控制反转) :将控制权授予容器以获取 object 的实例称为控制反转,这意味着您无需使用新运算符创建 object,而是让容器为您执行此操作。
DI(依赖注入) :将属性注入 object 的方式称为依赖注入。
我们有三种类型的依赖注入:
Spring 仅支持Constructor Injection和Setter/Getter Injection 。
由于所有答案都强调理论,我想用一个例子来证明:
假设我们正在构建一个应用程序,其中包含在订单发货后发送 SMS 确认消息的功能。 我们将有两个类,一个负责发送短信(SMSService),另一个负责捕获用户输入(UIHandler),我们的代码如下所示:
public class SMSService
{
public void SendSMS(string mobileNumber, string body)
{
SendSMSUsingGateway(mobileNumber, body);
}
private void SendSMSUsingGateway(string mobileNumber, string body)
{
/*implementation for sending SMS using gateway*/
}
}
public class UIHandler
{
public void SendConfirmationMsg(string mobileNumber)
{
SMSService _SMSService = new SMSService();
_SMSService.SendSMS(mobileNumber, "Your order has been shipped successfully!");
}
}
上面的实现没有错,但有几个问题:
-) 假设在开发环境中,您希望将发送的短信保存到文本文件而不是使用短信网关,以实现此目的; 我们最终会用另一个实现改变 (SMSService) 的具体实现,在这种情况下我们失去了灵活性并被迫重写代码。
-) 我们最终会混合类的职责,我们的 (UIHandler) 永远不应该知道 (SMSService) 的具体实现,这应该使用“接口”在类之外完成。 当实现这一点时,它将使我们能够通过将使用的(SMSService)与另一个实现相同接口的模拟服务交换来改变系统的行为,该服务会将 SMS 保存到文本文件而不是发送到 mobileNumber。
为了解决上述问题,我们使用将由我们的 (SMSService) 和新的 (MockSMSService) 实现的接口,基本上新的接口 (ISMSService) 将公开两种服务的相同行为,如下面的代码:
public interface ISMSService
{
void SendSMS(string phoneNumber, string body);
}
然后我们将改变我们的(SMSService)实现来实现(ISMSService)接口:
public class SMSService : ISMSService
{
public void SendSMS(string mobileNumber, string body)
{
SendSMSUsingGateway(mobileNumber, body);
}
private void SendSMSUsingGateway(string mobileNumber, string body)
{
/*implementation for sending SMS using gateway*/
Console.WriteLine("Sending SMS using gateway to mobile:
{0}. SMS body: {1}", mobileNumber, body);
}
}
现在我们将能够使用相同的接口创建具有完全不同实现的新模型服务(MockSMSService):
public class MockSMSService :ISMSService
{
public void SendSMS(string phoneNumber, string body)
{
SaveSMSToFile(phoneNumber,body);
}
private void SaveSMSToFile(string mobileNumber, string body)
{
/*implementation for saving SMS to a file*/
Console.WriteLine("Mocking SMS using file to mobile:
{0}. SMS body: {1}", mobileNumber, body);
}
}
此时,我们可以更改(UIHandler)中的代码以轻松使用服务(MockSMSService)的具体实现,如下所示:
public class UIHandler
{
public void SendConfirmationMsg(string mobileNumber)
{
ISMSService _SMSService = new MockSMSService();
_SMSService.SendSMS(mobileNumber, "Your order has been shipped successfully!");
}
}
我们在代码中实现了很大的灵活性并实现了关注点分离,但我们仍然需要对代码库进行更改以在两个 SMS 服务之间切换。 所以我们需要实现依赖注入。
为此,我们需要对 (UIHandler) class 构造函数进行更改以通过它传递依赖关系,通过这样做,使用 (UIHandler) 的代码可以确定要使用 (ISMSService) 的哪个具体实现:
public class UIHandler
{
private readonly ISMSService _SMSService;
public UIHandler(ISMSService SMSService)
{
_SMSService = SMSService;
}
public void SendConfirmationMsg(string mobileNumber)
{
_SMSService.SendSMS(mobileNumber, "Your order has been shipped successfully!");
}
}
现在将与 class (UIHandler) 对话的 UI 表单负责传递要使用的接口 (ISMSService) 的实现。 这意味着我们已经反转了控件,(UIHandler)不再负责决定使用哪个实现,调用代码负责。 我们已经实现了控制反转原理,DI 就是其中的一种。
UI表单代码如下:
class Program
{
static void Main(string[] args)
{
ISMSService _SMSService = new MockSMSService(); // dependency
UIHandler _UIHandler = new UIHandler(_SMSService);
_UIHandler.SendConfirmationMsg("96279544480");
Console.ReadLine();
}
}
与其直接对比 DI 和 IoC,不如从头开始可能会有所帮助:每个重要的应用程序都依赖于其他代码片段。
所以我正在写一个 class, MyClass
,我需要调用YourService
的一个方法......不知何故我需要获取一个YourService
的实例。 最简单、最直接的方法是自己实例化它。
YourService service = new YourServiceImpl();
直接实例化是获取依赖项的传统(过程)方式。 但它有许多缺点,包括MyClass
与YourServiceImpl
的紧密耦合,使我的代码难以更改和测试。 MyClass
并不关心YourService
的实现是什么样的,因此MyClass
不想负责实例化它。
我更愿意将这一责任从MyClass
转移到MyClass MyClass
。 最简单的方法就是将实例化调用( new YourServiceImpl();
)移动到其他一些 class 中。 我可以将另一个 class 命名为定位器、工厂或任何其他名称; 但关键是MyClass
不再负责YourServiceImpl
。 我已经颠倒了这种依赖关系。 伟大的。
问题是, MyClass
仍然负责调用 Locator/Factory/Whatever。 由于我为反转依赖所做的一切就是插入一个中间人,现在我与中间人耦合(即使我没有耦合到中间人给我的具体对象)。
我真的不在乎我的依赖项来自哪里,所以我宁愿不负责调用来检索它们。 反转依赖本身是不够的。 我想反转对整个过程的控制。
我需要的是MyClass
插入的一段完全独立的代码(称之为框架)。 那么我剩下的唯一责任就是声明我对YourService
的依赖。 该框架可以负责确定在何处、何时以及如何获取实例,并为MyClass
提供所需的内容。 最好的部分是MyClass
不需要了解框架。 框架可以控制这个依赖连接过程。 现在我已经反转了控制(在反转依赖项之上)。
有多种方法可以将MyClass
连接到框架中。 注入就是这样一种机制,我只需声明一个我希望框架提供的字段或参数,通常是在它实例化MyClass
时。
我认为所有这些概念之间的关系层次结构比该线程中的其他图表显示的要复杂一些; 但基本思想是它是一种层次关系。 我认为这与DIP 在野外同步。
但是 spring 文档说它们是相同的。
http://docs.spring.io/spring/docs/current/spring-framework-reference/htmlsingle/#beans-introduction
在第一行“ IoC 也称为依赖注入 (DI) ”。
IoC - 控制反转是通用术语,独立于语言,它实际上不是创建对象,而是描述正在创建 object 的方式。
DI - 依赖注入是一个具体的术语,其中我们通过使用不同的注入技术,即在运行时提供 object 的依赖关系。 Setter 注入、构造函数注入或通过接口注入。
控制反转是一种设计范式,旨在为应用程序的目标组件提供更多控制权,即完成工作的组件。
依赖注入是一种模式,用于创建其他对象所依赖的对象实例,而在编译时不知道将使用哪个 class 来提供该功能。
有几种基本技术可以实现控制反转。 这些是:
IOC(Inversion Of Control):将控制权交给容器以获取 object 的实例称为控制反转。 这意味着您不是使用new operator创建 object ,而是让容器为您执行此操作。
DI(依赖注入):将所需的参数(属性)从 XML 传递到对象(在 POJO CLASS 中)称为依赖注入。
DI和IOC是两种设计模式,主要侧重于提供组件之间的松散耦合,或者只是一种我们将 object 之间的常规依赖关系解耦以使对象彼此不紧密的方式。
通过以下示例,我试图解释这两个概念。
以前我们是这样写代码的
Public MyClass{
DependentClass dependentObject
/*
At somewhere in our code we need to instantiate
the object with new operator inorder to use it or perform some method.
*/
dependentObject= new DependentClass();
dependentObject.someMethod();
}
使用依赖注入,依赖注入器将负责对象的实例化
Public MyClass{
/* Dependency injector will instantiate object*/
DependentClass dependentObject
/*
At somewhere in our code we perform some method.
The process of instantiation will be handled by the dependency injector
*/
dependentObject.someMethod();
}
上述将控制权交给其他人(例如容器)进行实例化和注入的过程可以称为控制反转,而IOC容器为我们注入依赖项的过程可以称为依赖注入。
IOC是程序控制流倒置的原理:程序不是由程序员控制程序的流程,而是通过减少程序员的开销来控制流程。程序注入依赖项的过程称为直接投资
这两个概念协同工作,为我们提供了一种编写更灵活、可重用和封装的代码的方法,这使它们成为设计面向对象解决方案的重要概念。
也推荐阅读。
您还可以在此处查看我的类似答案之一
控制反转是软件架构的通用设计原则,有助于创建易于维护的可重用、模块化软件框架。
这是一种设计原则,其中控制流是从通用编写的库或可重用代码中“接收”的。
为了更好地理解它,让我们看看我们在早期的编码中是如何编码的。 在过程/传统语言中,业务逻辑通常控制应用程序的流程并“调用”通用或可重用的代码/功能。 例如,在一个简单的控制台应用程序中,我的控制流程由我的程序指令控制,其中可能包括对一些通用可重用函数的调用。
print ("Please enter your name:");
scan (&name);
print ("Please enter your DOB:");
scan (&dob);
//More print and scan statements
<Do Something Interesting>
//Call a Library function to find the age (common code)
print Age
相比之下,对于 IoC,框架是“调用”业务逻辑的可重用代码。
例如,在基于 windows 的系统中,已经有一个框架可用于创建 UI 元素,如按钮、菜单、windows 和对话框。 当我编写应用程序的业务逻辑时,框架的事件将调用我的业务逻辑代码(当事件被触发时),而不是相反。
虽然,框架的代码不知道我的业务逻辑,但它仍然知道如何调用我的代码。 这是使用事件/委托、回调等来实现的。这里的流控制是“反转的”。
因此,不是依赖于静态绑定对象的控制流,而是依赖于整个 object 图和不同对象之间的关系。
依赖注入是一种设计模式,它实现了 IoC 原则以解决对象的依赖关系。
简而言之,当您尝试编写代码时,您将创建和使用不同的类。 一个 class(A 类)可以使用其他类(B 类和/或 D 类)。 因此,Class B 和 D 是 class A 的依赖项。
一个简单的类比是 class 汽车。 一辆车可能依赖于其他类别,如发动机、轮胎等。
依赖注入建议不要使用依赖类(此处为 Class Car)创建其依赖项(Class Engine 和 class Tyre),而是应向 class 注入依赖项的具体实例。
让我们用一个更实际的例子来理解。 假设您正在编写自己的 TextEditor。 除此之外,您可以拥有一个拼写检查器,为用户提供检查其文本中的拼写错误的工具。 这种代码的简单实现可以是:
Class TextEditor
{
//Lot of rocket science to create the Editor goes here
EnglishSpellChecker objSpellCheck;
String text;
public void TextEditor()
{
objSpellCheck = new EnglishSpellChecker();
}
public ArrayList <typos> CheckSpellings()
{
//return Typos;
}
}
乍一看,一切看起来都很美好。 用户将编写一些文本。 开发人员将捕获文本并调用 CheckSpellings function 并将找到他将向用户显示的错字列表。
一切似乎都很好,直到有一天一位用户开始在编辑器中编写法语。
为了提供对更多语言的支持,我们需要更多的拼写检查器。 可能是法语、德语、西班牙语等。
在这里,我们创建了一个紧密耦合的代码,其中“English”SpellChecker 与我们的 TextEditor class 紧密耦合,这意味着我们的 TextEditor class 依赖于 EnglishSpellChecker,换句话说,EnglishSpellCheker 是 TextEditor 的依赖项。 我们需要去除这种依赖。 此外,我们的文本编辑器需要一种方法来根据开发人员在运行时的判断来保存任何拼写检查器的具体引用。
因此,正如我们在 DI 的介绍中看到的,它建议 class 应该注入其依赖项。 因此,将所有依赖项注入被调用的类/代码应该是调用代码的责任。 所以我们可以将我们的代码重构为
interface ISpellChecker
{
Arraylist<typos> CheckSpelling(string Text);
}
Class EnglishSpellChecker : ISpellChecker
{
public override Arraylist<typos> CheckSpelling(string Text)
{
//All Magic goes here.
}
}
Class FrenchSpellChecker : ISpellChecker
{
public override Arraylist<typos> CheckSpelling(string Text)
{
//All Magic goes here.
}
}
在我们的示例中,TextEditor class 应该接收 ISpellChecker 类型的具体实例。
现在,可以将依赖项注入构造函数、公共属性或方法中。
让我们尝试使用构造函数 DI 更改我们的 class。 更改后的 TextEditor class 将类似于:
Class TextEditor
{
ISpellChecker objSpellChecker;
string Text;
public void TextEditor(ISpellChecker objSC)
{
objSpellChecker = objSC;
}
public ArrayList <typos> CheckSpellings()
{
return objSpellChecker.CheckSpelling();
}
}
这样调用代码在创建文本编辑器时可以将适当的 SpellChecker Type 注入到 TextEditor 的实例中。
你可以在这里阅读完整的文章
IOC表示外部类管理应用程序的类,外部类表示容器管理应用程序class之间的依赖关系。 IOC的基本概念是程序员不需要创建您的对象,而是描述应该如何创建它们。
IoC 容器执行的主要任务是:实例化应用程序 class。 配置 object。 组装对象之间的依赖关系。
DI是通过使用 setter 注入或构造函数注入在运行时提供 object 的依赖项的过程。
国际奥委会-DIP-DI
1- IOC :描述一些软件架构设计的一个方面的抽象原则,其中与过程编程相比,系统的控制流是倒置的。
2- DIP : 是 Object 面向编程(OOP)原理(D of SOLID)。
3- DI :是一种实现控制反转并允许程序设计遵循依赖反转原则的软件设计模式。
IOC(控制反转)基本上是去除依赖关系并将它们解耦以使流程非线性的设计模式概念,并让容器/或另一个实体管理依赖关系的供应。 它实际上遵循好莱坞的原则“不要打电话给我们,我们会打电话给你”。 所以总结一下区别。
控制反转:- 这是一个通用术语,用于解耦依赖关系并委托它们的配置,这可以通过多种方式实现(事件、委托等)。
依赖注入:- DI 是 IOC 的一个子类型,通过构造函数注入、setter 注入或方法注入来实现。
下面的文章非常巧妙地描述了这一点。
https://www.codeproject.com/Articles/592372/Dependency-Injection-DI-vs-Inversion-of-Control-IO
我认为可以清楚地证明这个想法,而无需进入 Object 定向杂草,这似乎混淆了这个想法。
// dependency injection
function doSomething(dependency) {
// do something with your dependency
}
// in contrast to creating your dependencies yourself
function doSomething() {
dependency = getDependencySomehow()
}
// inversion of control
application = makeApp(authenticate, handleRequest, sendResponse)
application.run(getRequest())
// in contrast to direct control or a "library" style
application = makeApp()
request = application.getRequest()
if (application.authenticate(request.creds)) {
response = application.handleRequest(request)
application.sendResponse(response)
}
如果您歪着头眯着眼睛,您会发现 DI 是 IoC 的一种特殊实现,具有特定的关注点。 不是将模型和行为注入应用程序框架或高阶操作,而是将变量注入 function 或 object。
DIP vs DI vs IoC
[依赖倒置原则(DIP)]是SOLID
[关于]的一部分,它要求您使用抽象而不是实现
依赖注入 (DI) - 使用聚合而不是组合[关于]在这种情况下,外部 object 负责内部逻辑。 这使您可以拥有更多动态和可测试的方法
class A {
B b
//injecting B via constructor
init(b: B) {
self.b = b
}
}
控制反转(IoC)非常高级的定义,更多的是关于控制流。 最好的例子是Inversion of Control(IoC) Container or Framework
[About] 。 例如,您没有控件的框架 GUI,您可以做的一切只是实现框架的接口,当框架中发生某些操作时将调用该接口。 因此控制权从您的应用程序转移到正在使用的框架中
直插+直插
class A {
IB ib
init(ib: IB) {
self.ib = ib
}
}
您也可以使用以下方法实现它:
更复杂的例子
多层/模块结构中的依赖规则
伪代码:
interface InterfaceInputPort {
func input()
}
interface InterfaceOutputPort {
func output()
}
class A: InterfaceOutputPort {
let inputPort = B(outputPort: self)
func output() {
print("output")
}
}
class B: InterfaceInputPort {
let outputPort: InterfaceOutputPort
init(outputPort: InterfaceOutputPort) {
self.outputPort = outputPort
}
func input() {
print("input")
}
}
让我们从 SOLID 的 D 开始,看看 Scott Millett 的《Professional ASP.NET Design Patterns》一书中的 DI 和 IoC:
依赖倒置原则(DIP)
DIP就是将您的类与具体实现隔离开来,并让它们依赖于抽象类或接口。 它将编码的口头禅推广到接口而不是实现,从而通过确保您不与一个实现紧密耦合来增加系统内的灵活性。
依赖注入 (DI) 和控制反转 (IoC)
与 DIP 密切相关的是 DI 原则和 IoC 原则。 DI是通过构造函数、方法或属性提供低级别或依赖 class 的行为。 与 DI 一起使用,这些依赖类可以转换为接口或抽象类,这将导致高度可测试且易于更改的松散耦合系统。
在IoC中,与过程编程相比,系统的控制流是相反的。 这方面的一个例子是IoC 容器,其目的是将服务注入客户端代码,而无需客户端代码指定具体实现。 在这个实例中被反转的控制是客户端获取服务的行为。
米利特,C(2010 年)。 专业的 ASP.NET 设计模式。 威利出版社。 7-8。
//ICO,DI,10 年前,这就是他们的方式:
public class AuditDAOImpl implements Audit{
//dependency
AuditDAO auditDAO = null;
//Control of the AuditDAO is with AuditDAOImpl because its creating the object
public AuditDAOImpl () {
this.auditDAO = new AuditDAO ();
}
}
现在使用 Spring 3,4 或最新版本,如下所示
public class AuditDAOImpl implements Audit{
//dependency
//Now control is shifted to Spring. Container find the object and provide it.
@Autowired
AuditDAO auditDAO = null;
}
总体而言,控制从耦合代码的旧概念转换为 Spring 等框架,这使得 object 可用。 因此,据我所知,这就是 IOC,当我们使用构造函数或设置器将依赖的 object 注入另一个 object 时,这就是你所知道的依赖注入。 注入基本上意味着将其作为参数传递。 在 spring 我们有 XML 和基于注释的配置,我们定义 bean object 并传递依赖的 ZA8CFDE6331BD59EB66ZF 构造函数 9331BD59EB66ZF
我在Dzone.com上找到了最好的例子,这对于理解 IOC 和 DI 之间的真正区别非常有帮助
“IoC 就是当你让其他人为你创建对象时。” 因此,object 是由其他人创建的,而不是在您的代码中编写“new”关键字(例如,MyCode c=new MyCode())。 这个“其他人”通常被称为 IoC 容器。 这意味着我们将 rrsponsibility(控制)移交给容器以获取 object 的实例,这称为控制反转。这意味着您不是使用 new 运算符创建 object,而是让容器为您执行此操作。
DI(Dependency Injection): Way of injecting properties to an object is
called
Dependency injection.
We have three types of Dependency injection
1) Constructor Injection
2) Setter/Getter Injection
3) Interface Injection
Spring will support only Constructor Injection and Setter/Getter Injection.
1) DI 是 Child->obj 依赖于 parent-obj。 动词取决于很重要。 2)IOC是Child->obj在一个平台下执行。 平台可以是学校、学院、舞蹈 class。 这里 perform 是在任何平台提供商下具有不同含义的活动。
实际示例:`
//DI
child.getSchool();
//IOC
child.perform()// is a stub implemented by dance-school
child.flourish()// is a stub implemented by dance-school/school/
`
-AB
IoC 概念最初是在过程编程时代听到的。 因此,从历史背景来看,IoC 谈到了控制流所有权的倒置,即谁拥有按所需顺序调用函数的责任——无论是函数本身还是应该将其倒置给某个外部实体。
然而,一旦 OOP 出现,人们开始在 OOP 上下文中谈论 IoC,其中除了控制流之外,应用程序还关注 object 的创建及其关系。 此类应用程序希望反转对象创建(而不是控制流)的所有权,并需要一个容器来负责 object 创建、object 生命周期和注入应用程序对象的依赖关系,从而消除应用程序对象创建其他具体的 ZA8CFDE633119CB466.
从这个意义上说,DI 与 Io C 不同,因为它与控制流无关,但它是一种 Io* ,即对象创建所有权的反转。
这是 InversionOfControl(IOC) 和 DependencyInjection(DI) https://dzone.com/articles/ioc-vs-di 之间的区别
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.