【译】依赖注入

XXHolic
English

引子

在尝试运用 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;
    }
}

接口注入的优点是依赖项可以完全忽略它们的客户端,但是仍然可以接收对新客户端的引用,并使用它,将自身的引用发送回客户端。这样,依赖项就变成了注入器。关键是注入方法(可能只是一个经典的设置器方法)是通过一个接口提供的。

参考资料

阅读 513

[链接]

210 声望
1.1k 粉丝
0 条评论
你知道吗?

[链接]

210 声望
1.1k 粉丝
宣传栏