基于组件的开发/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");
}
MovieLister
与 MovieFinder
的一种实现 (ColonDelimitedMovieFinder
) 紧密绑定。这会导致强耦合。因此,方法 moviesDirectedBy
或 MovieLister
将无法使用 MovieFinder
的所有可能的形式(实现)。此外,在没有同时测试 ColonDelimitedMovieFinder
的情况下,无法测试方法 moviesDirectedBy
。另一个缺点是,MovieLister
到 MovieFinder
的依赖关系在方法签名(想想 Javadoc)中不可见,而只能在实现中可见。
解决这个问题的一个方法是,将依赖关系从外部注入到对象中,例如通过构造函数(构造函数注入)。不是类本身负责解析使用的具体接口实现,而是从外部注入。因此命名为“依赖 (Dependency) 注入 (Injection)”。
public MovieLister(MovieFinder finder) {
this.finder = finder;
}
这里,从构造函数签名可以看出依赖关系。
这是依赖注入 (DI) 的基础。这样做有以下优点:
- 更好的可复用性
- 减弱组件之间的耦合
- 可以进行隔离测试(注入模拟对象)
- 在方法或构造函数签名中识别依赖关系。