引子
在尝试运用 Scalable Frontend 1 — Architecture Fundamentals 里面所说的 Dependency Injection(依赖注入)时,感觉有些不清不楚,找了些资料,才更明白了一些,在此翻译记录部分聚合。
依赖注入是什么
在软件工程中,依赖注入是一种一个对象接收它所依赖的其它对象的技术。这些其它对象称为依赖。在典型的“使用”关系中,接收对象称为客户端(client),传递的(即“注入的”)对象称为服务(service)。将服务传递给客户端的代码可以是多种类型的东西,称为注入器。注入器告诉客户端要使用什么服务,而不是客户端指定要使用哪个服务。“注入”是指将依赖项(服务)传递到将使用它的对象(客户端)中。
服务是客户端状态的一部分。将服务传递给客户端,而不是让客户端构建或查找服务,是该模式的基本要求。
优势
- 依赖注入允许客户端灵活地进行配置。只有客户端的行为是固定的。客户端可以对任何支持客户端期望的内在接口的东西进行操作。
- 依赖注入可用于将系统的配置详细信息外部化到配置文件中,从而允许在不重新编译的情况下重新配置系统。可以针对组件需要不同实现的不同情况编写单独的配置。这包括但不限于测试。
- 因为依赖注入不需要对代码行为进行任何更改,所以它可以应用于遗留代码的重构。结果就是客户端更加独立,并且更容易使用存根或模拟对象进行单元测试。这种易测试性通常是使用依赖注入时注意到的第一个好处。
- 依赖注入允许客户端移除具体实现所需要使用的的所有知识。这有助于将客户端与设计更改和缺陷的影响隔离开来。它增强了可重用性、可测试性和可维护性。
- 减少应用程序对象中的样板代码,因为初始化或设置依赖项的所有工作都由提供程序组件处理。
- 依赖注入允许并行或独立开发。两个开发人员可以独立开发相互使用的类,而只需要知道类将通过什么接口进行通信。插件通常是由第三方商店开发的,他们甚至从不与开发使用插件的产品的开发人员交流。
- 依赖项注入减少了类与其依赖项之间的耦合。
劣势
- 依赖注入创建的客户端需要由构造代码提供配置细节。当明显的默认值可用时,这可能会繁重。
- 依赖注入会使代码难以跟踪(读取),因为它将行为与构造分离开来。这意味着开发人员必须引用更多的文件来跟踪系统的执行情况。
- 依赖注入框架是通过反射或动态编程实现的。这会妨碍 IDE 自动化的使用,例如“查找引用”、“显示调用层级”和安全重构。
- 依赖注入通常需要更多的前期开发工作,因为不能在需要的时间和地点召集某样东西成为正确的东西,但必须要求被注入,并且确保它已被注入。
- 依赖注入迫使复杂性从类转移到类之间的联系中,可能并不总是理想的或容易管理的。
- 依赖注入可能助长基于依赖注入框架的依赖。
依赖注入的几种形式
客户端对象至少有三种方式可以接收对外部模块的引用:构造器注入、设置器注入、接口注入。
构造器注入
依赖项是通过客户端的类构器提供的参数传入。
// Constructor
Client(Service service) {
// Save the reference to the passed-in service inside this client
this.service = service;
}
当可以首先构造所有依赖项时,最好使用它,因为它可以用来确保客户端对象始终处于有效状态,而不是使其某些依赖项引用为 null(不设置)。但就其本身而言,它缺乏在以后更改其依赖项的灵活性。这可能是使客户端不可变从而实现线程安全的第一步。
// Constructor
Client(Service service, Service otherService) {
if (service == null) {
throw new InvalidParameterException("service must not be null");
}
if (otherService == null) {
throw new InvalidParameterException("otherService must not be null");
}
// Save the service references inside this client
this.service = service;
this.otherService = otherService;
}
设置器注入
这种方式需要客户端提供一个设置器给依赖性。
// Setter method
public void setService(Service service) {
// Save the reference to the passed-in service inside this client.
this.service = service;
}
要求客户端为每个依赖项提供设置器。这样就可以随时自由地操作依赖项引用的状态。这提供了灵活性,但是如果要注入多个依赖项,那么客户端很难确保在能够使用之前已注入所有依赖项。
因为这些注入是独立发生的,所以无法判断注入器何时完成了与客户机的连接。依赖项可能仅仅因为调用其设置器失败而保留为 null 。这将强制检查从客户端组装时到使用时的注入是否已完成。
// Set the service to be used by this client
public void setService(Service service) {
this.service = service;
}
// Set the other service to be used by this client
public void setOtherService(Service otherService) {
this.otherService = otherService;
}
// Check the service references of this client
private void validateState() {
if (service == null) {
throw new IllegalStateException("service must not be null");
}
if (otherService == null) {
throw new IllegalStateException("otherService must not be null");
}
}
// Method that uses the service references
public void doSomething() {
validateState();
service.doYourThing();
otherService.doYourThing();
}
接口注入
这只是客户端将角色接口发布到客户端依赖项的设置方法。它可以用来确定在注入依赖项时注入器应该如何与客户端对话。
// Service setter interface.
public interface ServiceSetter {
public void setService(Service service);
}
// Client class
public class Client implements ServiceSetter {
// Internal reference to the service used by this client.
private Service service;
// Set the service that this client is to use.
@Override
public void setService(Service service) {
this.service = service;
}
}
接口注入的优点是依赖项可以完全忽略它们的客户端,但是仍然可以接收对新客户端的引用,并使用它,将自身的引用发送回客户端。这样,依赖项就变成了注入器。关键是注入方法(可能只是一个经典的设置器方法)是通过一个接口提供的。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。