控制反轉(Ioc)的設計原則
--減輕組件間的依賴性及藕合性的設計原則
作者:Make 創建日期:2005-07-24
導讀
1.緣由
2、回顧面向對象思想
3.什麼是Ioc
4、現行的幾種實現方式
5.結論
6.附錄
一、緣由
“萬事萬物皆有姻緣”,這一句話本是佛家偕語。不過筆者認爲,這一句話也真是道出了世間萬事萬物的相輔相成的最徹底的一句解釋。在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類移植使用另外一個公司的產品的時候,這個時候,要移植的量是不是還很大呢?
六、附錄
1、術語
控制反轉(Ioc-Inversion Of Control);
容器(container),用於組件創建,生存的環境;
框架(framework::一組用於對特定領域構造的可複用的基礎組件