深度理解依赖注入
[2] DI的实现方式
[3] Setter Injection
[4] 除了DI,还有Service Locator
1.依赖在哪里
老马举了一个小例子,是开发一个电影列举器(MovieList),这个电影列举器需要使用一个电影查找器(MovieFinder)提供的服务,伪码如下:
2public interface MovieFinder {
3 ArrayList findAll();
4}
5
6/*服务的消费者*/
7class MovieLister
8{
9 public Movie[] moviesDirectedBy(String arg) {
10 List allMovies = finder.findAll();
11 for (Iterator it = allMovies.iterator(); it.hasNext();) {
12 Movie movie = (Movie) it.next();
13 if (!movie.getDirector().equals(arg)) it.remove();
14 }
15 return (Movie[]) allMovies.toArray(new Movie[allMovies.size()]);
16 }
17
18 /*消费者内部包含一个将指向具体服务类型的实体对象*/
19 private MovieFinder finder;
20 /*消费者需要在某一个时刻去实例化具体的服务。这是我们要解耦的关键所在,
21 *因为这样的处理方式造成了服务消费者和服务提供者的强耦合关系(这种耦合是在编译期就确定下来的)。
22 **/
23 public MovieLister() {
24 finder = new ColonDelimitedMovieFinder("movies1.txt");
25 }
26}
从上面代码的注释中可以看到,MovieLister和ColonDelimitedMovieFinder(这可以使任意一个实现了MovieFinder接口的类型)之间存在强耦合关系,如下图所示:
图1
这使得MovieList很难作为一个成熟的组件去发布,因为在不同的应用环境中(包括同一套软件系统被不同用户使用的时候),它所要依赖的电影查找器可能是千差万别的。所以,为了能实现真正的基于组件的开发,必须有一种机制能同时满足下面两个要求:
(1)解除MovieList对具体MoveFinder类型的强依赖(编译期依赖)。
(2)在运行的时候为MovieList提供正确的MovieFinder类型的实例。
换句话说,就是在运行的时候才产生MovieList和MovieFinder之间的依赖关系(把这种依赖关系在一个合适的时候“注入”运行时),这恐怕就是Dependency Injection这个术语的由来。再换句话说,我们提到过解除强依赖,这并不是说MovieList和MovieFinder之间的依赖关系不存在了,事实上MovieList无论如何也需要某类MovieFinder提供的服务,我们只是把这种依赖的建立时间推后了,从编译器推迟到运行时了。
依赖关系在OO程序中是广泛存在的,只要A类型中用到了B类型实例,A就依赖于B。前面笔者谈到的内容是把概念抽象到了服务使用者和服务提供者的角度,这也符合现在SOA的设计思路。从另一种抽象方式上来看,可以把MovieList看成我们要构建的主系统,而MovieFinder是系统中的plugin,主系统并不强依赖于任何一个插件,但一旦插件被加载,主系统就应该可以准确调用适当插件的功能。
其实不管是面向服务的编程模式,还是基于插件的框架式编程,为了实现松耦合(服务调用者和提供者之间的or框架和插件之间的),都需要在必要的位置实现面向接口编程,在此基础之上,还应该有一种方便的机制实现具体类型之间的运行时绑定,这就是DI所要解决的问题。