1 ioc理論的背景 我們都知道,在采用面向對象方法設計的軟體系統中,它的底層實作都是由n個對象組成的,所有的對象通過彼此的合作,最終實作系統的業務邏輯。

圖1:軟體系統中耦合的對象
如果我們打開機械式手表的後蓋,就會看到與上面類似的情形,各個齒輪分别帶動時針、分針和秒針順時針旋轉,進而在表盤上産生正确的時間。圖1中描述的就是這樣的一個齒輪組,它擁有多個獨立的齒輪,這些齒輪互相齧合在一起,協同工作,共同完成某項任務。我們可以看到,在這樣的齒輪組中,如果有一個齒輪出了問題,就可能會影響到整個齒輪組的正常運轉。 齒輪組中齒輪之間的齧合關系,與軟體系統中對象之間的耦合關系非常相似。對象之間的耦合關系是無法避免的,也是必要的,這是協同工作的基礎。現在,伴随着工業級應用的規模越來越龐大,對象之間的依賴關系也越來越複雜,經常會出現對象之間的多重依賴性關系,是以,架構師和設計師對于系統的分析和設計,将面臨更大的挑戰。對象之間耦合度過高的系統,必然會出現牽一發而動全身的情形。
圖2:對象之間複雜的依賴關系
耦合關系不僅會出現在對象與對象之間,也會出現在軟體系統的各子產品之間,以及軟體系統和硬體系統之間。如何降低系統之間、子產品之間和對象之間的耦合度,是軟體工程永遠追求的目标之一。為了解決對象之間的耦合度過高的問題,軟體專家michael mattson提出了ioc理論,用來實作對象之間的“解耦”,目前這個理論已經被成功地應用到實踐當中,很多的j2ee項目均采用了ioc架構産品spring。
2 什麼是控制反轉(ioc) ioc是inversion of control的縮寫,多數書籍翻譯成“控制反轉”,還有些書籍翻譯成為“控制反向”或者“控制倒置”。 1996年,michael mattson在一篇有關探讨面向對象架構的文章中,首先提出了ioc 這個概念。對于面向對象設計及程式設計的基本思想,前面我們已經講了很多了,不再贅述,簡單來說就是把複雜系統分解成互相合作的對象,這些對象類通過封裝以後,内部實作對外部是透明的,進而降低了解決問題的複雜度,而且可以靈活地被重用和擴充。ioc理論提出的觀點大體是這樣的:借助于“第三方”實作具有依賴關系的對象之間的解耦,如下圖:
圖3:ioc解耦過程
大家看到了吧,由于引進了中間位置的“第三方”,也就是ioc容器,使得a、b、c、d這4個對象沒有了耦合關系,齒輪之間的傳動全部依靠“第三方”了,全部對象的控制權全部上繳給“第三方”ioc容器,是以,ioc容器成了整個系統的關鍵核心,它起到了一種類似“粘合劑”的作用,把系統中的所有對象粘合在一起發揮作用,如果沒有這個“粘合劑”,對象與對象之間會彼此失去聯系,這就是有人把ioc容器比喻成“粘合劑”的由來。 我們再來做個試驗:把上圖中間的ioc容器拿掉,然後再來看看這套系統:
圖4:拿掉ioc容器後的系統
我們現在看到的畫面,就是我們要實作整個系統所需要完成的全部内容。這時候,a、b、c、d這4個對象之間已經沒有了耦合關系,彼此毫無聯系,這樣的話,當你在實作a的時候,根本無須再去考慮b、c和d了,對象之間的依賴關系已經降低到了最低程度。是以,如果真能實作ioc容器,對于系統開發而言,這将是一件多麼美好的事情,參與開發的每一成員隻要實作自己的類就可以了,跟别人沒有任何關系! 我們再來看看,控制反轉(ioc)到底為什麼要起這麼個名字?我們來對比一下: 軟體系統在沒有引入ioc容器之前,如圖1所示,對象a依賴于對象b,那麼對象a在初始化或者運作到某一點的時候,自己必須主動去建立對象b或者使用已經建立的對象b。無論是建立還是使用對象b,控制權都在自己手上。
軟體系統在引入ioc容器之後,這種情形就完全改變了,如圖3所示,由于ioc容器的加入,對象a與對象b之間失去了直接聯系,是以,當對象a運作到需要對象b的時候,ioc容器會主動建立一個對象b注入到對象a需要的地方。 通過前後的對比,我們不難看出來:對象a獲得依賴對象b的過程,由主動行為變為了被動行為,控制權颠倒過來了,這就是“控制反轉”這個名稱的由來。
3 ioc的别名:依賴注入(di) 2004年,martin fowler探讨了同一個問題,既然ioc是控制反轉,那麼到底是“哪些方面的控制被反轉了呢?”,經過詳細地分析和論證後,他得出了答案:“獲得依賴對象的過程被反轉了”。控制被反轉之後,獲得依賴對象的過程由自身管理變為了由ioc容器主動注入。于是,他給“控制反轉”取了一個更合适的名字叫做“依賴注入(dependency injection)”。他的這個答案,實際上給出了實作ioc的方法:注入。所謂依賴注入,就是由ioc容器在運作期間,動态地将某種依賴關系注入到對象之中。
是以,依賴注入(di)和控制反轉(ioc)是從不同的角度的描述的同一件事情,就是指通過引入ioc容器,利用依賴關系注入的方式,實作對象之間的解耦。 我們舉一個生活中的例子,來幫助了解依賴注入的過程。大家對usb接口和usb裝置應該都很熟悉吧,usb為我們使用電腦提供了很大的友善,現在有很多的外部裝置都支援usb接口。
圖5:usb接口和usb裝置
現在,我們利用電腦主機和usb接口來實作一個任務:從外部usb裝置讀取一個檔案。 電腦主機讀取檔案的時候,它一點也不會關心usb接口上連接配接的是什麼外部裝置,而且它确實也無須知道。它的任務就是讀取usb接口,挂接的外部裝置隻要符合usb接口标準即可。是以,如果我給電腦主機連接配接上一個u盤,那麼主機就從u盤上讀取檔案;如果我給電腦主機連接配接上一個外置硬碟,那麼電腦主機就從外置硬碟上讀取檔案。挂接外部裝置的權力由我作主,即控制權歸我,至于usb接口挂接的是什麼裝置,電腦主機是決定不了,它隻能被動的接受。電腦主機需要外部裝置的時候,根本不用它告訴我,我就會主動幫它挂上它想要的外部裝置,你看我的服務是多麼的到位。這就是我們生活中常見的一個依賴注入的例子。在這個過程中,我就起到了ioc容器的作用。
通過這個例子,依賴注入的思路已經非常清楚:當電腦主機讀取檔案的時候,我就把它所要依賴的外部裝置,幫他挂接上。整個外部裝置注入的過程和一個被依賴的對象在系統運作時被注入另外一個對象内部的過程完全一樣。 我們把依賴注入應用到軟體系統中,再來描述一下這個過程: 對象a依賴于對象b,當對象 a需要用到對象b的時候,ioc容器就會立即建立一個對象b送給對象a。ioc容器就是一個對象制造工廠,你需要什麼,它會給你送去,你直接使用就行了,而再也不用去關心你所用的東西是如何制成的,也不用關心最後是怎麼被銷毀的,這一切全部由ioc容器包辦。
在傳統的實作中,由程式内部代碼來控制元件之間的關系。我們經常使用new關鍵字來實作兩個元件之間關系的組合,這種實作方式會造成元件之間耦合。ioc很好地解決了該問題,它将實作元件間關系從程式内部提到外部容器,也就是說由容器在運作期将元件間的某種依賴關系動态注入元件中。
4 ioc為我們帶來了什麼好處
我們還是從usb的例子說起,使用usb外部裝置比使用内置硬碟,到底帶來什麼好處? 第一、usb裝置作為電腦主機的外部裝置,在插入主機之前,與電腦主機沒有任何的關系,隻有被我們連接配接在一起之後,兩者才發生聯系,具有相關性。是以,無論兩者中的任何一方出現什麼的問題,都不會影響另一方的運作。這種特性展現在軟體工程中,就是可維護性比較好,非常便于進行單元測試,便于調試程式和診斷故障。代碼中的每一個class都可以單獨測試,彼此之間互不影響,隻要保證自身的功能無誤即可,這就是元件之間低耦合或者無耦合帶來的好處。 第二、usb裝置和電腦主機的之間無關性,還帶來了另外一個好處,生産usb裝置的廠商和生産電腦主機的廠商完全可以是互不相幹的人,各幹各事,他們之間唯一需要遵守的就是usb接口标準。這種特性展現在軟體開發過程中,好處可是太大了。每個開發團隊的成員都隻需要關心實作自身的業務邏輯,完全不用去關心其它的人工作進展,因為你的任務跟别人沒有任何關系,你的任務可以單獨測試,你的任務也不用依賴于别人的元件,再也不用扯不清責任了。是以,在一個大中型項目中,團隊成員分工明确、責任明晰,很容易将一個大的任務劃分為細小的任務,開發效率和産品品質必将得到大幅度的提高。
第三、同一個usb外部裝置可以插接到任何支援usb的裝置,可以插接到電腦主機,也可以插接到dv機,usb外部裝置可以被反複利用。在軟體工程中,這種特性就是可複用性好,我們可以把具有普遍性的常用元件獨立出來,反複利用到項目中的其它部分,或者是其它項目,當然這也是面向對象的基本特征。顯然,ioc不僅更好地貫徹了這個原則,提高了子產品的可複用性。符合接口标準的實作,都可以插接到支援此标準的子產品中。 第四、同usb外部裝置一樣,子產品具有熱插拔特性。ioc生成對象的方式轉為外置方式,也就是把對象生成放在配置檔案裡進行定義,這樣,當我們更換一個實作子類将會變得很簡單,隻要修改配置檔案就可以了,完全具有熱插撥的特性。
以上幾點好處,難道還不足以打動我們,讓我們在項目開發過程中使用ioc架構嗎?
5 ioc容器的技術剖析 ioc中最基本的技術就是“反射(reflection)”程式設計,目前.net c#、java和php5等語言均支援,其中php5的技術書籍中,有時候也被翻譯成“映射”。有關反射的概念和用法,大家應該都很清楚,通俗來講就是根據給出的類名(字元串方式)來動态地生成對象。這種程式設計方式可以讓對象在生成時才決定到底是哪一種對象。反射的應用是很廣泛的,很多的成熟的架構,比如象java中的hibernate、spring架構,.net中 nhibernate、spring.net架構都是把“反射”做為最基本的技術手段。
反射技術其實很早就出現了,但一直被忽略,沒有被進一步的利用。當時的反射程式設計方式相對于正常的對象生成方式要慢至少得10倍。現在的反射技術經過改良優化,已經非常成熟,反射方式生成對象和通常對象生成方式,速度已經相差不大了,大約為1-2倍的差距。 我們可以把ioc容器的工作模式看做是工廠模式的升華,可以把ioc容器看作是一個工廠,這個工廠裡要生産的對象都在配置檔案中給出定義,然後利用程式設計語言的的反射程式設計,根據配置檔案中給出的類名生成相應的對象。從實作來看,ioc是把以前在工廠方法裡寫死的對象生成代碼,改變為由配置檔案來定義,也就是把工廠和對象生成這兩者獨立分隔開來,目的就是提高靈活性和可維護性。
6 ioc容器的一些産品 sun one技術體系下的ioc容器有:輕量級的有spring、guice、pico container、avalon、hivemind;重量級的有ejb;不輕不重的有jboss,jdon等等。spring架構作為java開發中ssh(struts、spring、hibernate)三劍客之一,大中小項目中都有使用,非常成熟,應用廣泛,ejb在關鍵性的工業級項目中也被使用,比如某些電信業務。 .net技術體系下的ioc容器有:spring.net、castle等等。spring.net是從java的spring移植過來的ioc容器,castle的ioc容器就是windsor部分。它們均是輕量級的架構,比較成熟,其中spring.net已經被逐漸應用于各種項目中。
7 使用ioc架構應該注意什麼 使用ioc架構産品能夠給我們的開發過程帶來很大的好處,但是也要充分認識引入ioc架構的缺點,做到心中有數,杜絕濫用架構。 第一、軟體系統中由于引入了第三方ioc容器,生成對象的步驟變得有些複雜,本來是兩者之間的事情,又憑空多出一道手續,是以,我們在剛開始使用ioc架構的時候,會感覺系統變得不太直覺。是以,引入了一個全新的架構,就會增加團隊成員學習和認識的教育訓練成本,并且在以後的運作維護中,還得讓新加入者具備同樣的知識體系。 第二、由于ioc容器生成對象是通過反射方式,在運作效率上有一定的損耗。如果你要追求運作效率的話,就必須對此進行權衡。
第三、具體到ioc架構産品(比如:spring)來講,需要進行大量的配制工作,比較繁瑣,對于一些小的項目而言,客觀上也可能加大一些工作成本。 第四、ioc架構産品本身的成熟度需要進行評估,如果引入一個不成熟的ioc架構産品,那麼會影響到整個項目,是以這也是一個隐性的風險。 我們大體可以得出這樣的結論:一些工作量不大的項目或者産品,不太适合使用ioc架構産品。另外,如果團隊成員的知識能力欠缺,對于ioc架構産品缺乏深入的了解,也不要貿然引入。最後,特别強調運作效率的項目或者産品,也不太适合引入ioc架構産品,象web2.0網站就是這種情況。