已经发布了几个关于依赖注入的具体问题,例如何时使用它以及它有什么框架,
什么是依赖注入,何时/为什么应该或不应该使用它?
已经发布了几个关于依赖注入的具体问题,例如何时使用它以及它有什么框架,
什么是依赖注入,何时/为什么应该或不应该使用它?
当前回答
这是我见过的关于依赖注入和依赖注入容器的最简单的解释:
无依赖注入
应用程序需要Foo(例如控制器),因此:应用程序创建Foo应用程序调用FooFoo需要Bar(例如服务),因此:Foo创建BarFoo调用Bar酒吧需要Bim(服务、存储库,…),因此:条形图创建Bim酒吧有点事
使用依赖注入
应用程序需要Foo,需要Bar,需要Bim,因此:应用程序创建Bim应用程序创建Bar并赋予它Bim应用程序创建Foo并给它Bar应用程序调用FooFoo调用Bar酒吧有点事
使用依赖注入容器
应用程序需要Foo,因此:应用程序从容器中获取Foo,因此:容器创建Bim容器创建Bar并赋予它Bim容器创建Foo并给它Bar应用程序调用FooFoo调用Bar酒吧有点事
依赖注入和依赖注入容器是不同的:
依赖注入是一种编写更好代码的方法DI容器是帮助注入依赖项的工具
您不需要容器来执行依赖注入。然而,容器可以帮助您。
其他回答
例如,我们有两类客户机和服务。客户端将使用服务
public class Service {
public void doSomeThingInService() {
// ...
}
}
无依赖注入
方式1)
public class Client {
public void doSomeThingInClient() {
Service service = new Service();
service.doSomeThingInService();
}
}
方式2)
public class Client {
Service service = new Service();
public void doSomeThingInClient() {
service.doSomeThingInService();
}
}
方式3)
public class Client {
Service service;
public Client() {
service = new Service();
}
public void doSomeThingInClient() {
service.doSomeThingInService();
}
}
1) 2)3)使用
Client client = new Client();
client.doSomeThingInService();
优势
易于理解的
缺点
难以测试客户端类当我们更改Service构造函数时,我们需要在所有位置更改代码createService对象
使用依赖注入
方式1)构造函数注入
public class Client {
Service service;
Client(Service service) {
this.service = service;
}
// Example Client has 2 dependency
// Client(Service service, IDatabas database) {
// this.service = service;
// this.database = database;
// }
public void doSomeThingInClient() {
service.doSomeThingInService();
}
}
使用
Client client = new Client(new Service());
// Client client = new Client(new Service(), new SqliteDatabase());
client.doSomeThingInClient();
方式2)沉淀剂注入
public class Client {
Service service;
public void setService(Service service) {
this.service = service;
}
public void doSomeThingInClient() {
service.doSomeThingInService();
}
}
使用
Client client = new Client();
client.setService(new Service());
client.doSomeThingInClient();
方式3)接口注入
检查https://en.wikipedia.org/wiki/Dependency_injection
===
现在,这段代码已经遵循了依赖注入,测试客户端类更容易。然而,我们仍然多次使用新的Service(),并且在更改Service构造函数时效果不佳。为了防止这种情况,我们可以使用DI注射器1) 简单手动喷油器
public class Injector {
public static Service provideService(){
return new Service();
}
public static IDatabase provideDatatBase(){
return new SqliteDatabase();
}
public static ObjectA provideObjectA(){
return new ObjectA(provideService(...));
}
}
使用
Service service = Injector.provideService();
2) 使用库:适用于Android dagger2
优势
使测试更容易更改服务时,只需在Injector类中更改如果您使用使用构造函数注入,当您查看Client的构造函数时,您将看到Client类有多少依赖项
缺点
如果使用构造函数注入,则在创建客户端时创建服务对象,有时我们在客户端类中使用函数而不使用服务,因此创建的服务被浪费
依赖注入定义
https://en.wikipedia.org/wiki/Dependency_injection
依赖项是可以使用的对象(服务)注入是将依赖项(Service)传递给将使用它的依赖对象(Client)
使依赖注入概念易于理解。让我们以开关按钮为例来切换(打开/关闭)灯泡。
无依赖注入
Switch需要事先知道我连接到哪个灯泡(硬编码依赖项)。所以
开关->永久灯泡//开关直接连接到永久灯泡,测试不容易
Switch(){
PermanentBulb = new Bulb();
PermanentBulb.Toggle();
}
使用依赖注入
开关只知道我需要打开/关闭传递给我的灯泡。所以,
开关->灯泡1或灯泡2或夜灯泡(注入依赖性)
Switch(AnyBulb){ //pass it whichever bulb you like
AnyBulb.Toggle();
}
修改开关和灯泡的James示例:
public class SwitchTest {
TestToggleBulb() {
MockBulb mockbulb = new MockBulb();
// MockBulb is a subclass of Bulb, so we can
// "inject" it here:
Switch switch = new Switch(mockBulb);
switch.ToggleBulb();
mockBulb.AssertToggleWasCalled();
}
}
public class Switch {
private Bulb myBulb;
public Switch() {
myBulb = new Bulb();
}
public Switch(Bulb useThisBulbInstead) {
myBulb = useThisBulbInstead;
}
public void ToggleBulb() {
...
myBulb.Toggle();
...
}
}`
依赖注入是一种实践,其中对象的设计方式是从其他代码段接收对象实例,而不是在内部构造它们。这意味着可以在不更改代码的情况下替换实现对象所需接口的任何对象,这简化了测试,并改进了去耦。
例如,考虑这些类:
public class PersonService {
public void addManager( Person employee, Person newManager ) { ... }
public void removeManager( Person employee, Person oldManager ) { ... }
public Group getGroupByManager( Person manager ) { ... }
}
public class GroupMembershipService() {
public void addPersonToGroup( Person person, Group group ) { ... }
public void removePersonFromGroup( Person person, Group group ) { ... }
}
在本例中,PersonService::addManager和PersonService::removeManager的实现需要GroupMembershipService的实例才能完成其工作。如果没有依赖注入,传统的方法是在PersonService的构造函数中实例化一个新的GroupMembershipService,并在两个函数中使用该实例属性。但是,如果GroupMembershipService的构造函数有多个它需要的东西,或者更糟的是,需要在GroupMembershipServices上调用一些初始化“setter”,代码增长相当快,PersonService现在不仅依赖于GroupMembershipService,还依赖于GroupMembershipService所依赖的所有其他东西。此外,与GroupMembershipService的链接被硬编码到PersonService中,这意味着您不能为了测试目的而“虚拟”GroupMembershipServices,或者在应用程序的不同部分使用策略模式。
使用依赖注入,而不是在PersonService中实例化GroupMembershipService,您可以将其传递给PersonService构造函数,或者添加Property(getter和setter)来设置其本地实例,并与他们合作。这也意味着,任何属于GroupMembershipService的子类或实现GroupMembershipServices接口的内容都可以“注入”到PersonService中,PersonService不需要知道更改。
依赖注入(DI)的全部目的是保持应用程序源代码干净和稳定:
清除依赖项初始化代码无论使用的依赖关系如何稳定
实际上,每个设计模式都将关注点分开,以使将来的更改影响最小的文件。
DI的特定域是依赖配置和初始化的委托。
示例:带有shell脚本的DI
如果您偶尔在Java之外工作,请回想一下源代码在许多脚本语言(Shell、Tcl等,甚至在Python中被误用)中的使用情况。
考虑简单的dependent.sh脚本:
#!/bin/sh
# Dependent
touch "one.txt" "two.txt"
archive_files "one.txt" "two.txt"
脚本是依赖的:它无法单独成功执行(未定义存档文件)。
可以在archive_files_ip.sh实现脚本中定义archive_files(在本例中使用zip):
#!/bin/sh
# Dependency
function archive_files {
zip files.zip "$@"
}
您可以使用一个injector.sh“container”来包装这两个“components”,而不是直接在依赖脚本中源代码化实现脚本:
#!/bin/sh
# Injector
source ./archive_files_zip.sh
source ./dependent.sh
archive_files依赖项刚刚注入到依赖脚本中。
您可能已经注入了使用tar或xz实现archive_files的依赖项。
示例:删除DI
如果dependent.sh脚本直接使用依赖项,则该方法将被称为依赖项查找(与依赖项注入相反):
#!/bin/sh
# Dependent
# dependency look-up
source ./archive_files_zip.sh
touch "one.txt" "two.txt"
archive_files "one.txt" "two.txt"
现在的问题是依赖的“组件”必须自己执行初始化。
“组件”的源代码既不干净也不稳定,因为依赖项初始化中的每一次更改都需要“组件”源代码文件的新版本。
最后一句话
DI并不像Java框架那样被广泛强调和普及。
但这是一种通用的方法,可以解决以下问题:
应用程序开发(单一源代码发布生命周期)应用程序部署(具有独立生命周期的多个目标环境)
仅将配置与依赖项查找一起使用没有帮助,因为每个依赖项的配置参数数量(例如,新的身份验证类型)以及支持的依赖项类型数量(例如新的数据库类型)可能会发生变化。
简单来说,依赖注入(DI)是消除不同对象之间的依赖关系或紧密耦合的方法。依赖注入为每个对象提供一个内聚行为。
DI是国际奥委会春季原则的实施,该原则说“不要打电话给我们,我们会打电话给你”。使用依赖注入程序员不需要使用new关键字创建对象。
对象一旦加载到Spring容器中,我们就可以在需要时重用它们,方法是使用getBean(StringbeanName)方法从Spring容器中获取这些对象。