http://www.cnblogs.com/xingyukun/archive/2007/10/20/931331.html

3 篇文章 0 订阅

深度理解依赖注入(Dependence Injection) 

前面的话:提到依赖注入,大家都会想到老马那篇经典的文章。其实,本文就是相当于对那篇文章的解读。所以,如果您对原文已经有了非常深刻的理解,完全不需要再看此文;但是,如果您和笔者一样,以前曾经看过,似乎看懂了,但似乎又没抓到什么要领,不妨看看笔者这个解读,也许对您理解原文有一定帮助。1.依赖在哪里 老马举了一个小例子,是开发一个电影列举器(MovieList),这个电影列举器需要使用一个电影查找器(MovieFinder)提供的服务,伪码如下: 1/*服务的接口*/ 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 }1718 /*消费者内部包含一个将指向具体服务类型的实体对象*/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所要解决的问题。2.DI的实现方式 和上面的图1对应的是,如果我们的系统实现了依赖注入,组件间的依赖关系就变成了图2:图2说白了,就是要提供一个容器,由容器来完成(1)具体ServiceProvider的创建(2)ServiceUser和ServiceProvider的运行时绑定。下面我们就依次来看一下三种典型的依赖注入方式的实现。特别要说明的是,要理解依赖注入的机制,关键是理解容器的实现方式。本文后面给出的容器参考实现,均为黄忠成老师的代码,笔者仅在其中加上了一些关键注释而已。2.1 Constructor Injection(构造器注入) 我们可以看到,在整个依赖注入的数据结构中,涉及到的重要的类型就是ServiceUser, ServiceProvider和Assembler三者,而这里所说的构造器,指的是ServiceUser的构造器。也就是说,在构造ServiceUser实例的时候,才把真正的ServiceProvider传给他:1class MovieLister2{3 //其他内容,省略45 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。下面请看这个容器的参考代码:构造注入所需容器的伪码2.2 Setter Injection(设值注入) 这种注入方式和构造注入实在很类似,唯一的区别就是前者在构造函数的调用过程中进行注入,而它是通过给属性赋值来进行注入。无怪乎PicoContainer和Spring都是同时支持这两种注入方式。Spring对通过XML进行配置有比较好的支持,也使得Spring中更常使用设值注入的方式: 1 2 3 4 5 6 7 8 9 movies1.txt10 11 12下面也给出支持设值注入的容器参考实现,大家可以和构造器注入的容器对照起来看,里面的差别很小,主要的差别就在于,在获取对象实例(GetInstance)的时候,前者是通过反射得到待创建类型的构造器信息,然后根据构造器传入参数的类型在容器中进行查找,并构造出合适的实例;而后者是通过反射得到待创建类型的所有属性,然后根据属性的类型在容器中查找相应类型的实例。设值注入的容器实现伪码2.3 Interface Injection (接口注入) 这是笔者认为最不够优雅的一种依赖注入方式。要实现接口注入,首先ServiceProvider要给出一个接口定义:1public interface InjectFinder {2 void injectFinder(MovieFinder finder);3}接下来,ServiceUser必须实现这个接口:1class MovieLister: InjectFinder2{3 public void injectFinder(MovieFinder finder) {4 this.finder = finder;5 }6}容器所要做的,就是根据接口定义调用其中的inject方法完成注入过程,这里就不在赘述了,总的原理和上面两种依赖注入模式没有太多区别。2.4 除了DI,还有Service Locator 上面提到的依赖注入只是消除ServiceUser和ServiceProvider之间的依赖关系的一种方法,还有另一种方法:服务定位器(Service Locator)。也就是说,由ServiceLocator来专门负责提供具体的ServiceProvider。当然,这样的话ServiceUser不仅要依赖于服务的接口,还依赖于ServiceContract。仍然是最早提到过的电影列举器的例子,如果使用Service Locator来解除依赖的话,整个依赖关系应当如下图所示:图3用起来也很简单,在一个适当的位置(比如在一组相关服务即将被调用之前)对ServiceLocator进行初始化,用到的时候就直接用ServiceLocator返回ServiceProvider实例:1//服务定位器的初始化2ServiceLocator locator = new ServiceLocator();3locator.loadService("MovieFinder", new ColonMovieFinder("movies1.txt"));4ServiceLocator.load(locator);5//服务定义器的使用6//其实这个使用方式体现了服务定位器和依赖注入模式的最大差别:ServiceUser需要显示的调用ServiceLocator,从而获取自己需要的服务对象;7//而依赖注入则是隐式的由容器完成了这一切。8MovieFinder finder = (MovieFinder) ServiceLocator.getService("MovieFinder");9正因为上面提到过的ServiceUser对ServiceLocator的依赖性,从提高模块的独立性(比如说,你可能把你构造的ServiceUser或者ServiceProvider给第三方使用)上来说,依赖注入可能更好一些,这恐怕也是为什么大多数的IOC框架都选用了DI的原因。ServiceLocator最大的优点可能在于实现起来非常简单,如果您开发的应用没有复杂到需要采用一个IOC框架的程度,也许您可以试着采用它。3.广义的服务 文中很多地方提到服务使用者(ServiceUser)和服务提供者(ServiceProvider)的概念,这里的“服务”是一种非常广义的概念,在语法层面就是指最普通的依赖关系(类型A中有一个B类型的变量,则A依赖于B)。如果您把服务理解为WCF或者Web Service中的那种服务概念,您会发现上面所说的所有技术手段都是没有意义的。以WCF而论,其客户端和服务器端本就是依赖于Contract的松耦合关系,其实这也从另一个角度说明了SOA应用的优势所在。参考资料:Object Builder Application Block分类: 1.首页原创精华.NET区,软件设计好文要顶 关注我 收藏该文 EagleFish(邢瑜琨)关注 - 0粉丝 - 64+加关注18 1« 上一篇:(LitwareHR研究系列) WF in LitwareHR» 下一篇:《Inside Microsoft IL Assembler》学习笔记3:简述PE文件posted @ 2007-10-20 13:19 EagleFish(邢瑜琨) 阅读(40511) 评论(36) 编辑 收藏发表评论 #1楼 2007-10-20 13:24 | 蛙蛙王子 沙发一个,呵呵,支持(0)反对(0) #2楼 2007-10-20 14:39 | Justin 板凳一个支持(0)反对(0) #3楼[楼主] 2007-10-20 15:00 | EagleFish(邢瑜琨) 自己搬个马扎...... ^^支持(0)反对(0) #4楼 2007-10-20 22:03 | superzhanghuo水平不够没有看懂支持(0)反对(0) #5楼[楼主] 2007-10-21 11:01 | EagleFish(邢瑜琨) @superzhanghuo 汗一个.......俺还以为我写的比老马那篇要好懂一些呐 ^^支持(0)反对(0) #6楼 2007-10-21 16:30 | 张善友 这不就是翻译的Martin Fowler那篇文章 Inversion of Control Containers and the Dependency Injection pattern http://www.martinfowler.com/articles/injection.html支持(0)反对(0) #7楼[楼主] 2007-10-21 21:13 | EagleFish(邢瑜琨) @自由、创新、研究、探索 这不是翻译的。 原文的链接以及本文和原文的关系我已在第一段给出了。请您稍微仔细看看文章的内容再说 ^_^支持(0)反对(0) #8楼 2007-10-21 22:35 | 袁佳文 很好,比较直观,又复习了一下DI。支持(0)反对(0) #9楼 2007-10-22 09:56 | Clark Zheng 非常专业!支持(0)反对(0) #10楼[楼主] 2007-10-22 13:21 | EagleFish(邢瑜琨) @佳文 @Clark Zheng 多谢肯定。支持(0)反对(0) #11楼 2007-10-23 15:20 | DAbao写的挺好的,支持你。支持(0)反对(0) #12楼[楼主] 2007-10-23 15:28 | EagleFish(邢瑜琨) @DAbao 多谢您的鼓励。支持(0)反对(0) #13楼 2009-12-08 16:47 | 桃花源2看不懂水平不够也,主要里面讲解的实例太复杂了都 #14楼 2009-12-15 15:32 | 严不错,终于知道依赖注入是什么东西。不过就是类似“插件”的思想,也没什么特别 #15楼 2010-01-17 10:50 | 鹰的翅膀让我翱翔 手机看费眼啊!终于看了个似懂非懂!哈哈!楼主辛苦。支持(0)反对(0) #16楼 2010-03-01 19:01 | 倒着走路 受教了 收获很多 谢谢LZ支持(0)反对(0) #17楼 2010-12-15 05:26 | argyle 樓主解說很完整也很用心. 值得推薦.支持(0)反对(0) #18楼 2011-07-06 09:55 | jasonp List allMovies = finder.findAll(); 前面那个list 是什么对象支持(0)反对(0) #19楼 2011-07-06 10:06 | jasonp 不好意思 我是做.net的 误以为是.net语言支持(0)反对(0) #20楼 2012-03-19 16:33 | noanymorename 楼主好文支持(0)反对(1) #21楼 2012-07-30 11:43 | wangzj 15 private static Dictionary CreateConstructorParameter(Type targetType) 16 { 17 Dictionary paramArray = new Dictionary(); 1819 ConstructorInfo[] cis = targetType.GetConstructors(); 20 if (cis.Length > 1) 21 throw new Exception("target object has more then one constructor,container can't peek one for you."); 2223 foreach (ParameterInfo pi in cis[0].GetParameters()) 24 { 25 if (Stores.ContainsKey(pi.ParameterType)) 26 paramArray.Add(pi.Name, GetInstance(pi.ParameterType)); 27 } 28 return paramArray; 29 } 3031 public static object GetInstance(Type t) 32 { 33 if (Stores.ContainsKey(t)) 34 { 35 ConstructorInfo[] cis = t.GetConstructors(); 36 if (cis.Length != 0) 37 { 38 Dictionary paramArray = CreateConstructorParameter(t); 39 ListcArray = new List(); 40 foreach (ParameterInfo pi in cis[0].GetParameters()) 41 { 42 if (paramArray.ContainsKey(pi.Name)) 43 cArray.Add(paramArray[pi.Name]); 44 else 45 cArray.Add(null); 46 } 函数互相调用,怎么解释?支持(0)反对(0) #22楼 2012-10-23 13:59 | BigDataForFuture @ wangzj多练习下吧。。。这是一种递归处理依赖关系支持(0)反对(0) #23楼 2013-05-02 16:30 | semanwmj 为什么接口不是I开后的支持(1)反对(0) #24楼 2013-06-30 17:19 | 上山打老虎 @ semanwmj明显是java写的嘛.支持(0)反对(0) #25楼 2013-08-12 09:12 | 青空 写的非常好!让我深入理解了DI。支持(0)反对(0) #26楼 2013-09-13 14:17 | china688 我理解的:A类中有声明为B类的一个变量就是依赖,而B类的对象在调用者C实例化后传递给A,这就是注入,调用者C调用A中的方法实现了B的功能,这就是依赖注入。而注入依赖的时候大多传递接口或父类(多态性),配合工厂模式达到隔离类的目的,符合面向对象开闭原则支持(0)反对(0) #27楼 2014-01-02 22:01 | 我心明月 谢谢楼主!支持(0)反对(0) #28楼 2014-01-03 09:46 | chaoyuan 看不是很懂,,,楼主还有没有其他DI的资料推荐一下,想了解一下这方面的知识却找到很少的资料。先谢过了!支持(0)反对(0) #29楼 2014-01-03 11:06 | 我心明月 为毛老马那篇文章翻译成中文就域名报错,其他语言就可以呢,郁闷!!支持(0)反对(0) #30楼 2014-10-05 12:45 | heavensyu 看起来还是很累,可能语言组织有些问题,像ServiceUser或ServiceProvider或“容器”这些容易让人混淆的术语还是少用点比较好,假如能配合点设计模式说说就更好了。支持(0)反对(0) #31楼 2015-01-12 11:47 | JasonVV 先看了老马的文章,云里雾里;又看了lz的,感觉解释的太好了。只有一个疑问,对于service locator,前半部分代码应该是locator类的吧?后半部分才是user类的。Assembler在这其中又是什么作用呢?是否和DI作用一致,只不过不用再去找配置参数了,而是由locator直接提供呢?支持(0)反对(0) #32楼 2015-07-21 15:24 | X君 老马的文章在哪有没有链接?帮个忙谢了支持(0)反对(0) #33楼 2015-07-22 10:27 | X君 @ JasonVV请问老马的文章在哪,找不到,麻烦给个链接,谢了支持(0)反对(0) #34楼 2015-10-29 20:48 | ilcookie 第一个构造函数注入的话对于需要初始化的类有一定要求,对注入的类型的构造函数也有要求MoveList的构造函数必须只有一个,而且参数只能有一个,且类型是MoveFinderMoveFinder则必须是无参构造函数否则上面的实现会有点问题或者换个说法就是MoveFinder也通过构造注入实例化,这样也没问题另外Activator.CreateInstance(t, false); 这句没看懂,这个是java吗,语法不是很熟支持(0)反对(0) #35楼 2015-12-28 15:58 | Jony的后院 好文 顶一个支持(0)反对(0) #36楼 2016-12-21 16:14 | ZeXu 能力不足,不是看的很懂支持(0)反对(0)刷新评论刷新页面返回顶部注册用户登录后才能发表评论,请 登录 或 注册,访问网站首页。【推荐】50万行VC++源码: 大型组态工控、电力仿真CAD与GIS源码库【活动】一元专享1500元微软智能云Azure 最新IT新闻:· 小米影业首部电影找来刘德华“拆弹”,但依然前途未卜· 动视暴雪利润暴增60%!《守望先锋》无敌了· 弃2.4GHz!这就是全新Wi-Fi标准802.11ax· GitHub Commit数据揭示最流行的周末编程语言· Google正在为Play Store测试几项升级» 更多新闻...最新知识库文章:· 「代码家」的学习过程和学习经验分享· 写给未来的程序媛· 高质量的工程代码为什么难写· 循序渐进地代码重构· 技术的正宗与野路子» 更多知识库文章...昵称:EagleFish(邢瑜琨)园龄:9年10个月粉丝:64关注:0+加关注< 2007年10月 >日 一 二 三 四 五 六30 1 2 3 4 5 67 8 9 10 11 12 1314 15 16 17 18 19 2021 22 23 24 25 26 2728 29 30 31 1 2 34 5 6 7 8 9 10搜索 找找看 谷歌搜索常用链接我的随笔我的评论我的参与最新评论我的标签更多链接我的标签css(1)div(1)sql server query plan(1)随笔分类Asp.net(8)C#/.Net(12)CLR/IL(6)SAAS(2)Sql(SqlServer)(18)Tools(6)WCF(1)Windows管理(2)不发布文章技术日志(10)开发/调试经验(8)求职软件设计(4)算法(2)曾经的code(2)随笔档案2013年10月 (1)2013年1月 (1)2011年5月 (1)2011年3月 (4)2010年5月 (2)2010年4月 (1)2010年2月 (1)2010年1月 (2)2009年12月 (2)2009年11月 (1)2009年9月 (1)2009年8月 (2)2009年7月 (1)2009年6月 (1)2009年2月 (1)2008年12月 (1)2008年10月 (4)2008年8月 (1)2008年5月 (4)2008年4月 (4)2008年3月 (2)2008年1月 (1)2007年12月 (8)2007年11月 (4)2007年10月 (1)2007年9月 (2)2007年8月 (2)2007年7月 (9)2007年6月 (4)2007年5月 (2)2007年4月 (6)最新评论1. Re:深度理解依赖注入(Dependence Injection)能力不足,不是看的很懂--ZeXu2. Re:深度理解依赖注入(Dependence Injection)好文 顶一个--Jony的后院3. Re:深度理解依赖注入(Dependence Injection)第一个构造函数注入的话对于需要初始化的类有一定要求,对注入的类型的构造函数也有要求MoveList的构造函数必须只有一个,而且参数只能有一个,且类型是MoveFinderMoveFinder则必须是无......--ilcookie4. Re:深度理解依赖注入(Dependence Injection)@JasonVV请问老马的文章在哪,找不到,麻烦给个链接,谢了...--X君5. Re:深度理解依赖注入(Dependence Injection)老马的文章在哪有没有链接?帮个忙谢了--X君阅读排行榜1. 深度理解依赖注入(Dependence Injection)(40510)2. 从PowerDesigner概念设计模型(CDM)中的3种实体关系说起(24799)3. 从.Net类库代码来看Asp.net运行时(8082)4. Cannot perform alter on 'dbo.ObjectName' because it is an incompatible object type(5114)5. SAAS相关技术要点整理(4897)评论排行榜1. 从PowerDesigner概念设计模型(CDM)中的3种实体关系说起(51)2. 深度理解依赖注入(Dependence Injection)(36)3. 从.Net类库代码来看Asp.net运行时(30)4. 《Inside Microsoft IL Assembler》学习笔记4:.net中PE文件的结构(15)5. SAAS相关技术要点整理(14)推荐排行榜1. 深度理解依赖注入(Dependence Injection)(18)2. 从.Net类库代码来看Asp.net运行时(2)3. 从PowerDesigner概念设计模型(CDM)中的3种实体关系说起(2)4. Part I of Events in Asp.Net: Events in .Net(1)5. 《Inside Microsoft IL Assembler》学习笔记2:让IL代码简短些(1)


原文链接: http://www.cnblogs.com/xingyukun/archive/2007/10/20/931331.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值