跳转到内容

基于组件的开发/DI

来自维基教科书,开放世界中的开放书籍

为了弄清依赖注入 (DI) 的作用,首先需要描述的是它所指的依赖 (Dependency) 的类型。

在 DI 中,D 代表类之间或更准确地说,对应对象之间的依赖关系。

通常,较复杂的对象需要其他对象的某些功能,因此,这些对象存储在成员变量 (对象变量) 中。例如 (摘自 M. Fowler:控制反转容器和依赖注入模式,2004 https://martinfowler.com.cn/articles/injection.html

..
public class MovieLister {
  ...
  private MovieFinder ;
  ....
  
  public Movie[] moviesDirectedBy(String directorName) {
        List<Movie> allMovies = finder.findAll();
        for (Movie movie : allMovies){
            if (!movie.getDirector().equals(directorName)) {
               it.remove();
            }
        }
        return allMovies.toArray();
    }
}

方法 moviesDirectedBy 使用了一个类型为 MovieFinder 的对象。人们会说 MovieFinder **依赖于** MovieLister

MovieFinder 是一个接口,它可以有多种实现(DBMovieFinder, ColonDelimiterMovieFinder)。

public interface MovieFinder {
  List<Movie> findAll();
}


但是,如果直接在 MovieLister 的构造函数中创建具体的 MovieFinder 实例,则这种可复用性将无法实现,例如:

class MovieLister
...
  private MovieFinder finder;
  public MovieLister() {
    finder = new ColonDelimitedMovieFinder("movies1.txt");
  }

MovieListerMovieFinder 的一种实现 (ColonDelimitedMovieFinder) 紧密绑定。这会导致强耦合。因此,方法 moviesDirectedByMovieLister 将无法使用 MovieFinder 的所有可能的形式(实现)。此外,在没有同时测试 ColonDelimitedMovieFinder 的情况下,无法测试方法 moviesDirectedBy。另一个缺点是,MovieListerMovieFinder 的依赖关系在方法签名(想想 Javadoc)中不可见,而只能在实现中可见。

解决这个问题的一个方法是,将依赖关系从外部注入到对象中,例如通过构造函数(构造函数注入)。不是类本身负责解析使用的具体接口实现,而是从外部注入。因此命名为“依赖 (Dependency) 注入 (Injection)”。


  public MovieLister(MovieFinder finder) {
    this.finder = finder;
  }

这里,从构造函数签名可以看出依赖关系。


这是依赖注入 (DI) 的基础。这样做有以下优点:

  • 更好的可复用性
  • 减弱组件之间的耦合
  • 可以进行隔离测试(注入模拟对象)
  • 在方法或构造函数签名中识别依赖关系。
华夏公益教科书