文鹏教育

博客园 首页 新随笔 联系 订阅 管理
老头子 @ 2005-06-30 12:34(转载)

背景
        流行的、时髦的东西,是特别容易识别的,因为你经常会碰见它们。芙蓉姐姐是这么让我给碰上的,今天想说的IoC也是这么给我碰上的。为了给自己的blog 带来点人气,老头子决定也开始走走流行路线。所以,今天我们就来动一动虽然没有芙蓉姐姐那么火,但是也算火的IoC。

Dependency Injection
       IoC,全名叫做Inversion of Control,中文名叫控制反转。我对IoC比较全面的理解,来自于Martin Fowler的这篇文章。Martin Fowler是一位很务实的设计师,他说,IoC,叫做Dependency Injection,控制注入,要更准确一些。所以他的那篇文章的题目就叫Dependency Injection模式。但是,Ioc这个名字好象更火一些,所以,我还是沿用IoC这个名字好了。

       老头子的目标,是用最短的篇幅来介绍IoC,并且告诉各位IoC其实是一个很旧的概念。这是老头子的兴趣所在。

铺垫
       IoC模式,处理的是对象之间的依赖关系,这是为什么Martin先生说Dependency Injection更准确的原因。所以,我们先来看看对象依赖。

       比如有两个对象cA和cB。其中cA依赖于cB。通常我们会看到这样的(伪)代码:
       
       class cA...
               private cB b;
               public cA(){
                       b = new cB();
               }        

       此后,cA对象的方法,就可以直接引用cB的实例b。

        这样的代码并没有啥不妥,但是很明显,cA没有对接口进行依赖,而是依赖于cB的实现,这违反了对接口而不是实现编程的原则。这样做的不好之处在于:如果 cB的实现改变了,比如改用了另一个class,cA的代码就需要做出修改。虽然新的cB实现了和老的cB相同的接口,但是替换实现导致了使用者代码的更 改,这是不好的行为。也就是说cA被cB粘住了。

咋改
       首先,改成这个样子:
       
       interface iB ...
       class cB implements iB ...
       class cA ...
               private iB b;
               public cA(){
                       b = (iB)(new cB());
               }        

        这样改了之后,代码好看一些,心情也舒坦一些,但是,跟没改差不多。没有解决 根本问题,cA还是要负责创建cB实例,还是得依赖cB。但是,首先iB被分出来了,为下一步改进做好了准备。
       
       iB 被分离出来,有一个隐含的好处。就是cA代码的修改,不必担心会使用到特定的cB实现暴露出来的公用方法,因为cA对cB的使用,已经被严格地限定在iB 所声明的方法集之内。这样一来,cB的实现,只要实现一个iB接口,其它的可以海阔天空。无需担心由于其它public方法被cA不小心引用而被cA粘 住。

解耦
       接口被分离出来之后,剩下的,就是怎么将 new CB() 这个代码从cA里面拿出去。这就是说,怎么样把生成cB实例的任务交给其它对象去做。

       其实问题的关键,就是怎么将iB实例设置到cA.b上面去。这有两种方法:

       一种就是所谓的IoC。它通过cA对象的某个方法将一个作为外部参数传入进来的iB实例赋给cA.b。根据这个方法的不同,人们把IoC又分为三种类型,虽然在我看来它们是一回事。

       还有一种,叫做Service Locator。就是把生成实例的任务,委托给一个周知的对象。把对其它对象的依赖,变成对该对象的依赖。这样的话,只要该对象能稳定,问题就不大了。

IoC
       先说IoC的三种方法:

       1。构造子注入,也叫type 3 IoC。实际上就是用构造函数来给cA.b赋值:
       
       class cA ...
               public cA(iB ib){
                       b = ib;
               }        

       2。设值方法,也叫type 2 IoC。用一个set×××方法来给cA.b赋值:
       
       class cA ...
               public void setiB(iB ib){
                       b = ib;
               }        

       3。接口注入,也叫type 1 IoC。cA开发一个接口,让框架通过该接口来给cA.b赋值:
       
       public interface InjectiB{
               void injectiB(iB ib);
       }
       class cA implements InjectiB ...
               public void injectiB(iB ib){
                       b = ib;
               }        

        这三种方式,形式有所不同,实质没啥区别。总之,都是提供接口让IoC容器来 调用,并通过这些暴露的接口来完成依赖的注入。因为这个赋值方法都是被IoC容器或外部应用调用,控制权从cA转移到外部框架去了。这就是控制反转名称的 由来。

       使用IoC之后,应用代码大致如下:
       
               ......
               iB b = (iB)(new cB());
               cA a = new cA(b);
               或者
               cA a = new cA();
               a.setiB(b);
               或者
               cA a = new cA();
               a.injectiB(b);
               ......        

       做得好的IoC容器,可以将 new cB() new cA() 的过程全部通过配置和容器来自动完成。

Service Locator
       相对来说,Service Locator更简单直观一些,它的代码形如:
       
       class cA ...
               private iB b = serviceLocator.getInstance("iB");        

       其中,serviceLocator可以实现为一个Singleton,或者作为一个静态成员传递给cA。因为它是周知的对象,这不是问题。

事实上
        在我看来,IoC和ServiceLocator模式,并没有太大的差别。它们都将对象的实例化交给框架或第三方去完成,而当使用者需要对象的时候,对象 已经准备好。差别只在于,对于IoC模式来说,对象赋值是由外部代码完成的,使用者处于被动地位;而对于ServiceLocator来说,对象赋值是使 用者主动请求完成的,它处于主动地位。

       对于这两种模式的比较,意义并不太大。到底使用哪一个,取决于个人的偏好。老头子在这里也就不多说什么了。

旧东西
        我们只要回忆一下COM框架,就会知道IoC和ServiceLocator在那里其实都是旧东西。当微软把Automation再添加到COM架构中去 的时候,我们不能不感叹,COM是个好东西。憎恨微软当然没有问题,但依我看,对于微软,不服恐怕也是不行的。

       至少,产品中处处体现出先进的设计理念的微软,没有象Java社群这样喜欢鼓噪新的概念。微软能够赢得最终用户的心,那也不是没有理由的。

预告
       左手剑的下一击,我想讲讲同样火的AOP是个什么东西,以及,为什么说它也是个旧东西。

Trackback地址: http://www.yculblog.com/trackback/c/746748

posted on 2005-11-30 22:05  zhoup  阅读(458)  评论(0)    收藏  举报