已经发布了几个关于依赖注入的具体问题,例如何时使用它以及它有什么框架,
什么是依赖注入,何时/为什么应该或不应该使用它?
已经发布了几个关于依赖注入的具体问题,例如何时使用它以及它有什么框架,
什么是依赖注入,何时/为什么应该或不应该使用它?
当前回答
例如,我们有两类客户机和服务。客户端将使用服务
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)
其他回答
“依赖注入”不就是指使用参数化构造函数和公共setter吗?
James Shore的文章展示了以下示例进行比较。
没有依赖注入的构造函数:公共类示例{私有数据库Thingie myDatabase;public Example(){myDatabase=新数据库Thingie();} public void doStuff(){... myDatabase.getData();... } } 具有依赖注入的构造函数:公共类示例{私有数据库Thingie myDatabase;公共示例(DatabaseThingie使用ThisDatabaseReplace){myDatabase=改用此数据库;}public void doStuff(){... myDatabase.getData();... } }
什么是依赖注入(DI)?
正如其他人所说,依赖注入(DI)消除了直接创建和管理我们感兴趣的类(消费者类)所依赖的其他对象实例(在UML意义上)的生命周期的责任。这些实例通常作为构造函数参数或通过属性设置器传递给我们的消费者类(依赖对象实例化和传递给消费者类的管理通常由控制反转(IoC)容器执行,但这是另一个主题)。
DI、DIP和固体
具体来说,在Robert C Martin的面向对象设计的SOLID原则的范例中,DI是依赖反转原则(DIP)的可能实现之一。DIP是SOLID咒语的D——其他DIP实现包括服务定位器和插件模式。
DIP的目标是解耦类之间紧密、具体的依赖关系,相反,通过抽象来放松耦合,这可以通过接口、抽象类或纯虚拟类来实现,具体取决于所使用的语言和方法。
如果没有DIP,我们的代码(我称之为“消费类”)直接耦合到一个具体的依赖项,并且经常承担着知道如何获取和管理该依赖项实例的责任,即概念上:
"I need to create/use a Foo and invoke method `GetBar()`"
然而,在应用DIP后,这一要求被放宽,获得和管理Foo依赖寿命的担忧已经消除:
"I need to invoke something which offers `GetBar()`"
为什么使用DIP(和DI)?
以这种方式解耦类之间的依赖性允许用其他实现轻松替换这些依赖性类,这些实现也满足抽象的前提条件(例如,依赖性可以与同一接口的另一个实现切换)。此外,正如其他人所提到的,通过DIP解耦类的最常见原因可能是允许单独测试消费类,因为这些依赖关系现在可以被清除和/或嘲笑。
DI的一个结果是依赖对象实例的寿命管理不再由消费类控制,因为依赖对象现在被传递到消费类(通过构造函数或setter注入)。
这可以用不同的方式来看待:
如果需要保留消费类对依赖项的生命周期控制,则可以通过将用于创建依赖类实例的(抽象)工厂注入消费类来重新建立控制。消费者将能够根据需要通过工厂上的Create获取实例,并在完成后处理这些实例。或者,依赖实例的生命周期控制可以放弃给IoC容器(下面将详细介绍)。
何时使用DI?
在可能需要用依赖性替代等效实现的情况下,任何时候,如果您需要对类的方法进行单元测试,依赖项的生命周期的不确定性可能需要进行实验(例如,嘿,MyDepClass是线程安全的-如果我们将其设置为单例并将同一实例注入所有消费者,会怎么样?)
实例
这里是一个简单的C#实现。给定以下消费类:
public class MyLogger
{
public void LogRecord(string somethingToLog)
{
Console.WriteLine("{0:HH:mm:ss} - {1}", DateTime.Now, somethingToLog);
}
}
虽然看似无害,但它对另外两个类System.DateTime和System.Console有两个静态依赖关系,这不仅限制了日志输出选项(如果没有人在监视,则日志记录到控制台将毫无价值),而且更糟糕的是,考虑到对非确定性系统时钟的依赖关系,很难自动测试。
然而,我们可以将DIP应用于这个类,方法是将时间戳问题抽象为依赖项,并将MyLogger仅耦合到一个简单的接口:
public interface IClock
{
DateTime Now { get; }
}
我们还可以将对Console的依赖放宽为抽象,例如TextWriter。依赖注入通常实现为构造函数注入(将抽象作为参数传递给依赖项作为消费类的构造函数)或Setter注入(通过setXyz()Setter或定义了{set;}的.Net Property传递依赖项)。构造函数注入是首选的,因为这样可以保证类在构造后处于正确的状态,并允许将内部依赖字段标记为只读(C#)或最终(Java)。因此,在上面的示例中使用构造函数注入,这就给我们留下了:
public class MyLogger : ILogger // Others will depend on our logger.
{
private readonly TextWriter _output;
private readonly IClock _clock;
// Dependencies are injected through the constructor
public MyLogger(TextWriter stream, IClock clock)
{
_output = stream;
_clock = clock;
}
public void LogRecord(string somethingToLog)
{
// We can now use our dependencies through the abstraction
// and without knowledge of the lifespans of the dependencies
_output.Write("{0:yyyy-MM-dd HH:mm:ss} - {1}", _clock.Now, somethingToLog);
}
}
(需要提供一个具体的Clock,它当然可以恢复到DateTime。现在,这两个依赖关系需要由IoC容器通过构造函数注入提供)
可以构建一个自动化的单元测试,这无疑证明了我们的记录器工作正常,因为我们现在可以控制依赖关系-时间,我们可以监视书面输出:
[Test]
public void LoggingMustRecordAllInformationAndStampTheTime()
{
// Arrange
var mockClock = new Mock<IClock>();
mockClock.Setup(c => c.Now).Returns(new DateTime(2015, 4, 11, 12, 31, 45));
var fakeConsole = new StringWriter();
// Act
new MyLogger(fakeConsole, mockClock.Object)
.LogRecord("Foo");
// Assert
Assert.AreEqual("2015-04-11 12:31:45 - Foo", fakeConsole.ToString());
}
下一步
依赖注入总是与控制反转容器(IoC)相关联,以注入(提供)具体的依赖实例,并管理生命周期实例。在配置/引导过程中,IoC容器允许定义以下内容:
每个抽象和配置的具体实现之间的映射(例如“消费者请求IBar时,返回ConcreteBar实例”)可以为每个依赖项的生命周期管理设置策略,例如为每个消费者实例创建新对象,在所有消费者之间共享单一依赖项实例,仅在同一线程之间共享同一依赖项实例等。在.Net中,IoC容器了解IDisposable等协议,并将根据配置的生命周期管理来负责处理依赖关系。
通常,一旦IoC容器被配置/引导,它们就可以在后台无缝地运行,从而让编码器专注于手头的代码,而不用担心依赖性。
DI友好代码的关键是避免类的静态耦合,并且不要使用new()创建依赖项
根据上面的示例,依赖关系的解耦确实需要一些设计工作,对于开发人员来说,需要进行范式转换,以打破直接添加依赖关系的习惯,转而信任容器来管理依赖关系。
但好处很多,特别是能够彻底测试你感兴趣的班级。
注意:POCO/POJO/Serialization DTO/Entity Graphs/Anonymous JSON投影等(即“仅数据”类或记录)的创建/映射/投影(通过新的..())不被视为依赖项(在UML意义上),也不受DI的约束。使用new来投射这些是很好的。
例如,我们有两类客户机和服务。客户端将使用服务
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)
来自Book Apress.Spring.Persistence.with.HHibernate,2010年10月
依赖注入的目的是将解决应用程序业务中的外部软件组件逻辑。如果没有依赖注入访问所需的服务可能会与组件的密码这不仅增加了出错的可能性,还增加了代码膨胀,并放大了维护复杂性;它耦合组件更紧密地结合在一起,使得在重构或测试。
依赖注入(DI)是设计模式中的一种,它使用了OOP的基本特性——一个对象与另一个对象之间的关系。虽然继承继承一个对象以实现更复杂和更具体的另一个对象,但关系或关联只需使用属性从一个对象创建指向另一对象的指针。DI的功能与OOP的其他特性相结合,如接口和隐藏代码。假设图书馆里有一个客户(订阅者),为了简单起见,他只能借一本书。
书本界面:
package com.deepam.hidden;
public interface BookInterface {
public BookInterface setHeight(int height);
public BookInterface setPages(int pages);
public int getHeight();
public int getPages();
public String toString();
}
接下来我们可以有很多种书;其中一种类型是虚构:
package com.deepam.hidden;
public class FictionBook implements BookInterface {
int height = 0; // height in cm
int pages = 0; // number of pages
/** constructor */
public FictionBook() {
// TODO Auto-generated constructor stub
}
@Override
public FictionBook setHeight(int height) {
this.height = height;
return this;
}
@Override
public FictionBook setPages(int pages) {
this.pages = pages;
return this;
}
@Override
public int getHeight() {
// TODO Auto-generated method stub
return height;
}
@Override
public int getPages() {
// TODO Auto-generated method stub
return pages;
}
@Override
public String toString(){
return ("height: " + height + ", " + "pages: " + pages);
}
}
现在,用户可以与图书建立关联:
package com.deepam.hidden;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
public class Subscriber {
BookInterface book;
/** constructor*/
public Subscriber() {
// TODO Auto-generated constructor stub
}
// injection I
public void setBook(BookInterface book) {
this.book = book;
}
// injection II
public BookInterface setBook(String bookName) {
try {
Class<?> cl = Class.forName(bookName);
Constructor<?> constructor = cl.getConstructor(); // use it for parameters in constructor
BookInterface book = (BookInterface) constructor.newInstance();
//book = (BookInterface) Class.forName(bookName).newInstance();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (SecurityException e) {
e.printStackTrace();
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
return book;
}
public BookInterface getBook() {
return book;
}
public static void main(String[] args) {
}
}
这三个类都可以隐藏起来,以便实现自己的功能。现在我们可以将此代码用于DI:
package com.deepam.implement;
import com.deepam.hidden.Subscriber;
import com.deepam.hidden.FictionBook;
public class CallHiddenImplBook {
public CallHiddenImplBook() {
// TODO Auto-generated constructor stub
}
public void doIt() {
Subscriber ab = new Subscriber();
// injection I
FictionBook bookI = new FictionBook();
bookI.setHeight(30); // cm
bookI.setPages(250);
ab.setBook(bookI); // inject
System.out.println("injection I " + ab.getBook().toString());
// injection II
FictionBook bookII = ((FictionBook) ab.setBook("com.deepam.hidden.FictionBook")).setHeight(5).setPages(108); // inject and set
System.out.println("injection II " + ab.getBook().toString());
}
public static void main(String[] args) {
CallHiddenImplBook kh = new CallHiddenImplBook();
kh.doIt();
}
}
如何使用依赖注入有许多不同的方法。可以将它与Singleton等结合起来,但基本上它只是通过在另一个对象内创建对象类型的属性来实现的关联。它的有用性是唯一的,也是唯一的特点,我们应该反复编写的代码总是为我们准备好并做好准备。这就是为什么DI如此紧密地与控制反转(IoC)绑定,这意味着我们的程序将控制传递给另一个正在运行的模块,该模块将bean注入到我们的代码中。(可以被注入的每个对象都可以被签名或被认为是一个Bean。)例如,在Spring中,它是通过创建和初始化ApplicationContext容器来完成的,这对我们来说很有用。我们只需在代码中创建Context并调用初始化Bean。此时注射已自动完成。