背景

       

流行的、时髦的东西,是特别容易识别的,因为你经常会碰见它们。芙蓉姐姐是这么让我给碰上的,今天想说的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是个什么东西,以及,为什么说它也是个旧东西。