控制反转(Ioc)的设计原则

--减轻组件间的依赖性及藕合性的设计原则

一、缘由
     “万事万物皆有姻缘”,这一句话本是佛家偕语。不过笔者认为,这一句话也真是道出了世间万事万物的相辅相成的最彻底的一句解释。在IT这一行,更新换代比光速还快,我想同仁们都不会反驳我这句话吧!要不试试,从艺术性的工作,到工程化的项目,从面向过程到面向对象,从面向对象到面向方面!每一种思想都是那么的伟大,每一种思想都没有任何一个人(还真是任何一个人,不信的话,你找几个大师级的人物问问,他们对哪一种思想完全理解并应用了?)完全理解并应用他,但新的思想又出来了(这种思想说的是软件类的思想,编程的思想,当然没有孔子孟子之类说)!好像说了这么多,跟“万事万物皆有姻缘”没有任何关系一样,有,当然有,虽然这么多的思想没有任何一个人完全贯通使用,但提出一种新的思想来使用,这是事物发展的需要,也是必然的趋势,艺术性的编程不能实用于大型应用,因此提出工程化,面向过程使软件升级特别困难,因此懒惰的人们提出了面向对象的复用思想
,但面向对象的思想还是复杂,懒惰的人们就想要做更少的工作,因此提出了面向方面!“万事万物皆有姻缘”因为人们的懒惰,所以该放下的就放下了,重新拿起新的事物!那么为什么现在在编程界又提出了控制反转(Ioc)呢?下面我们看一个经常引用的例子:
  public Class MyClass{
    Logger _logger=Logger.getLogger(this.getClass());//实例化_logger   
    public void helloWorld(String){
       _logger.DEBUG("Start print .....");
       System.out.println("Hello! The world!");
       _logger.DEBUG("End print.......");
    }    
  }
 
  现在想,如果我实例化_logger的方式变了,变成了:
  Logger _logger=Logger.getLogger("My Class DEBUG");
  你们想想是不是应该要改上面这个已经实现了的CLASS呢?结果是肯定的,如果我有10个类,100个,那是不是要改10次,100次,我们称这样的软件开发为藕合性太强或依赖太深!
“万事万物皆有姻缘”,控制反转(Ioc)就是为了解决这样的问题而提出的原则!
二、面向对象思想回顾
    首先我们回顾面向对象思想的一些内容,笔者在《软件工程之面向对象软件工程基础(软件工程实践之三)》一文中概术面向对象时,面向对象=类+对象+继承+通信,面向对象就是这样的一种思想。将组件分类,使用时再实例化,类与类之间可以继承,他们通过通信联系。在这里我们要重新到一个概念是服务,在很多面向对象文章中都提到了服务,那么什么是服务呢?即一个类向另一个类提供他想要的接口(或方法)。
如下例:
public ClassA{
   /*
    *下面是ClassA提供给其它类使用的接口
    */
   public void bussiness(){
      System.out.println("execute bussiness");
   }
}

public ClassB{
   ClassA classA=new ClassA();//classA即将向ClassB提供服务
   public executebussiness(){
     classA.bussiness();//调用了ClassA的bussiness方法来得到服务
   }
}
另两个概念就是服务提供者与服务使用者,从上例可以看出,ClassA就是服务提供者-提供业务服务的一方 ,ClassB就服务使用者(也称消费者)-使用业务服务的一方,在他们之间采用的联系的方式我们叫做通信。   从上面的例子可以看出,服务提供者提供服务给消费者是特别直接的。这样有如下坏处:
   如果服务提供者变了呢?这样是不是需要改动每个服务消费者?   这样的一种依赖关系?我们用什么来解决呢?Ioc,控制反转,它让服务消费者不直接依赖于服务提供者!
三、什么是控制反转(Ioc)?
    一种让服务消费者不直接依赖于服务提供者一 种组件设计方式,一种减少类与类之间依赖的设计原则,一种使服务与服务提供者完全分开的设计原则。我们将第一个例子改进一下:
    public Class MyClass{
       Logger _logger;
       public void setLogger(Logger logger){
          this._logger=logger;
       }
       public void helloWorld(){
          _logger.DEBUG("Start DEBUG print....");
          System.out.println(“hello world");
          _logger.DEBUG("End DEBUG print.....");
       }
   }
  
 现在调用时:
 public Class UseLogger{
   Logger _logger=Logger.getLogger(this.getClass());
   public void execute(){
     MyClass myClass=new MyClass();
     myClass.setLogger(_logger);
     myClass.helloWorld();
  }
}
这样,我们发现,整个使用Logger的控制权全部在客户端,即Logger给MyClass提供的服务的方式已经提交给了最终客房,完全取决于客户端的需要,而不是在MyClass调用服务时就依赖于Logger了,这样,服务提供者(Logger)与消费者(MyClass)之间的依赖就少了很多。这就是Ioc的一种具体实现方式。
   我们可以看出来,Ioc更通俗的一种解释就是:我在需要服务时,才告诉你怎么去给我提供服务_logger的定义在MyClass中只是一个预留的定义,直到UseLogger最终消费时才将具体怎么使用Logger通知给MyClass.

四、控制反转的几种设计方式?
   目前控制反转的几种实现方式:
   .基于方法的(Method-based Ioc,Type-0)
   .基于接口的(Interface-based Ioc,Type-1)
   .基于设值的(Setter-based Ioc,Type-2)
   .基于构造的(Construtor-based Ioc,Type-3)
.Type-0
基于方法的实现,主要依赖的是接口,,服务提供者将自己的接品传递给消费者方法,而不是将具体的实现类型传递过去,如下:
public interface MyClass{
   public void helloWorld(Logger _logger);
}
调用时:
Logger _logger=Logger.getLogger(this.getClass());
MyClass myClass=new MyClassImpl();
myClass.helloWorld(_logger);
从上面可以看出,这样Logger与MyClass的具体服务都依赖于接口MyClass,Logger提供服务时也止于方法上,Logger服务提供与
MyClassImpl消费之间隔开了,
.Type-1
基于接口的实现,主要是采用容器来管理服务提供者,消费者只要实现服务提供者的接口就可以达到服务的目的了。
如下:
Public Class MyClassImpl implements MyClass,Logger {
  protected Logger _logger;
  /*
   *实现Logger的enableLogger有效方法
   */
   public void enableLogger(Logger logger){
     _logger=logger;
  }
  public void helloWorld(){
   _logger.DEBUG("ENABLE");
  }
}
怎么调用 :
MyClass myClass=new MyClass();
Logger _logger=Logger.getLogger(this.getClass());
Container.enableLogger(myClass,_logger);
myClass.helloWorld();
可以看出,这样的实现,必须要有珍上容器来检查是否实现了Logger接品,如果实现凶才能有效,否则就没有作用。这样的方法大大的减少了类与类之间的依赖.但必须要实现一个容器。而且发现,每一个实现的类都直接跟服务提供者(本例就是Logger)的接口有了很直接的联系。
.Type-2
     的方式,就是通过BEAN的setter方法来给成员变量赋值,我们采用这种方式,就可以通过一个配置文件,配置好相应的Bean,然后通过一个容器来得到这个服务Bean,这样组件之间的依赖就已经少了。
如:
public Class MyClass{
  private Logger logger;
  public void setLogger(Logger logger){
    this.logger=logger;
  }
  public void helloWorld(){
   logger.DEBUG("ENABLE");
 }
}
如Spring采用的方式就是如此,它的全过程如下:
配置文件:
<Beans>
  <Bean id="logger" Class="Logger"/>
  <Bean id="myClass" Class=MyClass">
    <property name="logger"><ref bean="logger"/></property>
  </Bean>
</Beans>
调用:
InputStream is=Client.class.getClassLoader().getResourceAsStream("bean.xml");
BeanFactory beans=new XMLBeanFacotory(is);
MyClass myClass=(MyClass)beans.getBean("myClass");
myClass.helloWorld();
从上面可以看出,所有的服务提供类及消费类都已经通过一个容器类来提取配置文件统一管理了。

.Type-3
基于构造的Ioc,顾名思义,就是通过构造子来传递服务提供者的具体实例来实例化服务接口。
如下:
public Class MyClass{
  private Logger _logger;
  public MyClass(Logger logger){
    _logger=logger;
  }
  public void helloWorld(){
    _logger.DEBUG("ENABLE");
  }
}
这样,注册服务组件时在构造方法处完成,但,这样一来继承和构造时都有一定的困难。
五、结语
        Ioc的一个真正的目的就是移植,减少组件的依赖性!但我们通过上面的例子,有没有发现,这中间的依赖还很大呢?比如:如果我要将Logger类移植使用另外一个公司的产品的时候,这个时候,要移植的量是不是还很大呢?

posted on 2008-10-13 20:40  美丽心情11  阅读(298)  评论(0编辑  收藏  举报