您的位置:知识库 » 软件设计

深度理解依赖注入

作者: EagleFish(邢瑜琨)  来源: 博客园  发布时间: 2009-03-13 11:01  阅读: 97403 次  推荐: 14   原文链接   [收藏]  
摘要:提到依赖注入,大家都会想到老马那篇经典的文章。其实,本文就是相当于对那篇文章的解读。所以,如果您对原文已经有了非常深刻的理解,完全不需要再看此文;但是,如果您和笔者一样,以前曾经看过,似乎看懂了,但似乎又没抓到什么要领,不妨看看笔者这个解读,也许对您理解原文有一定帮助。
[1] 依赖在哪里
[2] DI的实现方式
[3] Setter Injection
[4] 除了DI,还有Service Locator

2.DI的实现方式
   和上面的图1对应的是,如果我们的系统实现了依赖注入,组件间的依赖关系就变成了图2:
图2
说白了,就是要提供一个容器,由容器来完成(1)具体ServiceProvider的创建(2)ServiceUser和ServiceProvider的运行时绑定。下面我们就依次来看一下三种典型的依赖注入方式的实现。特别要说明的是,要理解依赖注入的机制,关键是理解容器的实现方式。本文后面给出的容器参考实现,均为黄忠成老师的代码,笔者仅在其中加上了一些关键注释而已。

2.1 Constructor Injection(构造器注入)
 我们可以看到,在整个依赖注入的数据结构中,涉及到的重要的类型就是ServiceUser, ServiceProvider和Assembler三者,而这里所说的构造器,指的是ServiceUser的构造器。也就是说,在构造ServiceUser实例的时候,才把真正的ServiceProvider传给他:

 

1class MovieLister
2{
3   //其他内容,省略
4
5   public MovieLister(MovieFinder finder)
6   {
7       this.finder = finder;
8   }

9}

接下来我们看看Assembler应该如何构建:

 1private MutablePicoContainer configureContainer() {
 2    MutablePicoContainer pico = new DefaultPicoContainer();
 3    
 4    //下面就是把ServiceProvider和ServiceUser都放入容器的过程,以后就由容器来提供ServiceUser的已完成依赖注入实例,
 5    //其中用到的实例参数和类型参数一般是从配置档中读取的,这里是个简单的写法。
 6    //所有的依赖注入方法都会有类似的容器初始化过程,本文在后面的小节中就不再重复这一段代码了。
 7    Parameter[] finderParams =  {new ConstantParameter("movies1.txt")};
 8    pico.registerComponentImplementation(MovieFinder.class, ColonMovieFinder.class, finderParams);
 9    pico.registerComponentImplementation(MovieLister.class);
10    //至此,容器里面装入了两个类型,其中没给出构造参数的那一个(MovieLister)将依靠其在构造器中定义的传入参数类型,在容器中
11    //进行查找,找到一个类型匹配项即可进行构造初始化。
12    return pico;
13}

需要在强调一下的是,依赖并未消失,只是延后到了容器被构建的时刻。所以正如图2中您已经看到的,容器本身(更准确的说,是一个容器运行实例的构建过程)对ServiceUser和ServiceProvoder都是存在依赖关系的。所以,在这样的体系结构里,ServiceUser、ServiceProvider和容器都是稳定的,互相之间也没有任何依赖关系;所有的依赖关系、所有的变化都被封装进了容器实例的创建过程里,符合我们对服务应用的理解。而且,在实际开发中我们一般会采用配置文件来辅助容器实例的创建,将这种变化性排斥到编译期之外。
   即使还没给出后面的代码,你也一定猜得到,这个container类一定有一个GetInstance(Type t)这样的方法,这个方法会为我们返回一个已经注入完毕的MovieLister。 一个简单的应用如下:

1public void testWithPico() 
2{
3    MutablePicoContainer pico = configureContainer();
4    MovieLister lister = (MovieLister) pico.getComponentInstance(MovieLister.class);
5    Movie[] movies = lister.moviesDirectedBy("Sergio Leone");
6    assertEquals("Once Upon a Time in the West", movies[0].getTitle());
7}

上面最关键的就是对pico.getComponentInstance的调用。Assembler会在这个时候调用MovieLister的构造器,构造器的参数就是当时通过pico.registerComponentImplementation(MovieFinder.class, ColonMovieFinder.class, finderParams)设置进去的实际的ServiceProvider--ColonMovieFinder。下面请看这个容器的参考代码:

构造注入所需容器的伪码

 

14
3

软件设计热门文章

    软件设计最新文章

      最新新闻

        热门新闻