(轉)那些年搞不懂的高深術語——依賴倒置•控制反轉•依賴注入•面向接口編程

做者:在好
連接:http://www.zhihu.com/question/20821697/answer/102234360
來源:知乎
著做權歸做者全部,轉載請聯繫做者得到受權。

那些年搞不懂的高深術語——依賴倒置•控制反轉•依賴注入•面向接口編程

那些年,空氣中彷彿還能聞到漢唐盛世的餘韻,所以你決不容許本身的臉上有油光,時刻保持活力。然而,你必定曾爲這些「高深術語」感到過困擾。也許時至今日,你仍對它們只知其一;不知其二。不過就在今天,這一切都將完全改變!我將帶領你以一種全新的高清視角進入奇妙的編程世界,領略涵泳在這些「高深術語」中的活潑潑的地氣,以及翩躚於青萍之末的雲水禪心。html


·內聚

內聚,通俗的來說,就是本身的東西本身保管,本身的事情本身作。算法

經典理論告訴咱們,程序的兩大要素:一個是數據(data),一個是操做(opration)。而 PASCAL之父Nicklaus Wirth則進一步提出了「程序 = 數據結構 + 算法」的著名公式。雖然提法上有所差別,可是其根本內涵倒是一致的,微妙的差異在於,「數據 + 操做」是微觀的視域,「數據結構 + 算法」則是中觀的視域。而在宏觀的視域下,我認爲「程序 = 對象 + 消息」。對象是什麼?對象就是保管好本身的東西,作好本身的事情的程序模塊——這就是內聚!傳統的面向過程編程方法因爲割裂了數據結構和算法,使得軟件的內聚性廣泛低迷,曾一度引起了軟件危機。試想,你們都本身的東西很差好保管,本身的事情也很差好作,不引起危機纔怪呢!固然,對象的內聚只是內聚的一個層次,在不一樣的尺度下其實都有內聚的要求,好比方法也要講內聚,架構也要講內聚。編程

《周易·彖傳》中講「乾道變化,各正性命,保合太和,乃利貞」,就是要求每個個體因循着各自的稟賦而努力成就各自的品性,而後各自保全,彼此和合,最終達成宇宙的完滿狀態。《論語·憲問》中,子路問君子。子曰:「修己以敬。」曰:「如斯而已乎?」曰:「修己以安人」,更是明確的教導咱們要不斷提升自身的內聚性,最大限度地減小給他人形成的麻煩,從而達到安人、安百姓、安天下的目標。我想,成長的過程就是一個不斷提高內聚的過程。「本身的東西本身保管,本身的事情本身作」,這些孩提時代的教誨,放到今天仍能讓很多「大人」臉紅不已。太多的人保管很差本身的「東西」,保管很差本身的身體,保管很差本身的婚姻,更保管很差本身如蛛絲般震顫飄蕩的狂亂的心。至於作好本身的事情,則更是惘然,甚至不少人連本身的事情是什麼都搞不清楚,所以渾渾噩噩,飽食終日。內聚,是一個值得咱們好好反思的問題。 設計模式

·依賴·耦合

在面向對象編程中,對象自身是內聚的,是保管好本身的數據,完成好本身的操做的,而對外界呈現出本身的狀態和行爲。可是,沒有絕對的自力更生,對外開放也是必要的!一個對象,每每須要跟其餘對象打交道,既包括獲知其餘對象的狀態,也包括仰賴其餘對象的行爲,而一旦這樣的事情發生時,咱們便稱該對象依賴於另外一對象。只要兩個對象之間存在一方依賴一方的關係,那麼咱們就稱這兩個對象之間存在耦合。 好比媽媽和baby,媽媽要隨時關注baby的睡、醒、困、哭、尿等等狀態,baby則要仰賴媽媽的餵奶、哄睡、換紙尿褲等行爲,從程序的意義上說,兩者互相依賴,所以也存在耦合。首先要說,耦合是必要的。咱們來看如下這個實驗。api

【王陽明與山中之花網絡


View Code

因爲王陽明這個對象不依賴山花這個對象,又沒有其餘的方式來獲知山花的怒放狀態,因此他要麼選擇不說,要麼瞎說,但不說編譯是通不過,而瞎說做爲王陽明來說也是通不過的,因此這個系統是沒法成立的。要想系統成立,必需要這樣寫:數據結構


public bool AdmireFlowers()
        {
            return flower.IsBloomed; ; 
        }

不管這個山花對象是怎麼來的,做爲參數傳入仍是做爲屬性設置、仍是在內部構造出來,總之,王陽明與山花之間發生了依賴,兩者之間產生了耦合。 固然,這是一個很淺顯的問題。有趣的是王陽明對此事的見解:「你未看花時,花與你同寂;你來看花,花於你則一時分明起來。可見心外無物!」王陽明講的是對的!「心外無物」翻譯技術語言是這樣的:不存在耦合的兩個對象必然拿不到對方的引用!架構

·耦合度·解耦和

耦合的程度就是耦合度,也就是雙方依賴的程度。上文所說的媽媽和baby就是強耦合。而你跟快遞小哥之間則是弱耦合。通常來講耦合度太高並非一件好事。就拿做爲IT精英的你來講吧,上級隨時敦促你的工做進度,新手頻繁地須要你指導問題,隔三差五還須要參加酒局飯局,而後還要每天看領導的臉色、關注老婆的心情,而後你還要關注代碼中的bug 、bug、bug,和需求的變化、變化、變化,都夠焦頭爛額了,還猝不及防的要關注眼睛、頸椎、前列腺和頭髮的狀態,而後你再炒個股,這些加起來大概就是個強耦合了。從某種意義上來講,耦合天生就與自由爲敵,不管是其餘對象依賴於你,仍是你依賴其餘對象。好比有人嗜煙、酗酒,你有多依賴它們就有多不自由;好比有人家裏生了七八個娃,還有年邁的父母、岳父母,他們有多依賴你,你就有多不自由。因此老子這樣講:「五音使人耳聾,五色使人目盲,馳騁狩獵使人心發狂,可貴之貨使人行妨。」盧梭也是不無悲涼的說「人生而自由,卻又無往而不在枷鎖中」。所以,要想自由,就必需要下降耦合,而這個過程就叫作解耦和。框架

·依賴倒置(Dependence Inversion Principle)

解耦和最重要的原則就是依賴倒置原則:數據結構和算法

高層模塊不該該依賴底層模塊,他們都應該依賴抽象。抽象不該該依賴於細節,細節應該依賴於抽象。

《資本論》中都曾闡釋依賴倒轉原則——在商品經濟的萌芽時期,出現了物物交換。假設你要買一個IPhone,賣IPhone的老闆讓你拿一頭豬跟他換,但是你並無養豬,你只會編程。因此你找到一位養豬戶,說給他作一個養豬的APP來換他一頭豬,他說換豬能夠,可是得用一條金項鍊來換——因此這裏就出現了一連串的對象依賴,從而形成了嚴重的耦合災難。解決這個問題的最好的辦法就是,買賣雙發都依賴於抽象——也就是貨幣——來進行交換,這樣一來耦合度就大爲下降了。

再舉一個編程中的依賴倒置的例子。咱們知道,在通訊中,消息的收發和消息的處理每每密不可分。就通常的通訊框架而言,消息的收發一般是已經實現了的,而消息的處理則是須要用戶來自定義完成的。先看一個正向依賴的例子:輕量級通訊引擎StriveEngine。tcpServerEngine是StriveEngine.dll提供通訊引擎,它發佈有一個MessageReceived事件。假設我定義了一個CustomizeHandler類來用於消息處理,那麼CustomizeHandler的內部須要預約tcpServerEngine的MessageReceived事件,所以customizeHandler依賴於tcpServerEngine,這就是一個普通的依賴關係,也就是高層模塊依賴於低層模塊。


ESFramework通訊框架應用了依賴倒轉原則。ESFramework定義了一個IcustomizeHandler接口,用戶在進行消息處理時,實現該接口,而後將其注入到rapidPassiveEngine客戶端通訊引擎之中。


View Code

很明顯,相比於上一個例子,這裏的依賴關係變成了rapidPassiveEngine依賴於customizeHandler,也就是說依賴關係倒置了過來,上層模塊再也不依賴於底層模塊,而是它們共同依賴於抽象。rapidPassiveEngine依賴的是IcustomizeHandler接口類型的參數,customizeHandler一樣是以實現的接口的方式依賴於IcustomizeHandler——這就是一個依賴倒置的典範。

·控制反轉(Inversion of Control)

控制反轉跟依賴倒置是一模一樣的兩個概念,當存在依賴倒置的時候每每也存在着控制反轉。可是控制反轉也有本身的獨特內涵。

首先咱們要區分兩個角色,server 跟 Client,也就是服務方和客戶方。提供服務端的一方稱爲服務方,請求服務的一方稱爲客戶方。咱們最熟悉的例子就是分佈式應用的C/S架構,服務端和客戶端。其實除此以外,C/S關係到處可見。好比在TCP/IP協議棧中,咱們知道,每層協議爲上一層提供服務,那麼這裏就是一個C/S關係。當咱們使用開發框架時,開發框架就是做爲服務方,而咱們本身編寫的業務應用就是客戶方。當Client調用server時,這個叫作通常的控制;而當server調用Client時,就是咱們所說的控制反轉,同時咱們也將這個調用稱爲「回調」。控制反轉跟依賴倒置都是一種編程思想,依賴倒置着眼於調用的形式,而控制反轉則着眼於程序流程的控制權。通常來講,程序的控制權屬於server,而一旦控制權交到Client,就叫控制反轉。好比你去下館子,你是Client餐館是server。你點菜,餐館負責作菜,程序流程的控制權屬於server;而若是你去自助餐廳,程序流程的控制權就轉到Client了,也就是控制反轉。


控制反轉的思想體如今諸多領域。好比事件的發佈/ 訂閱就是一種控制反轉,GOF設計模式中也多處體現了控制反轉,好比典型的模板方法模式等。而開發框架則是控制反轉思想應用的集中體現。好比以前所舉的ESFramework通訊框架的例子,通訊引擎回調用戶自定義的消息處理器,這就是一個控制反轉。以及ESFramework回調用戶自定義的羣組關係和好友關係,回調用戶自定義的用戶管理器以管理在線用戶相關狀態,回調用戶自定義的登錄驗證處理,等等不一而足。再好比與ESFramework一脈相承的輕量級通訊引擎StriveEngine,經過回調用戶自定義的通訊協議來實現更加靈活的通訊。

由此咱們也能夠總結出開發框架與類庫的區別:使用開發框架時,框架掌握程序流程的控制權,而使用類庫時,則是應用程序掌握程序流程的控制權。或者說,使用框架時,程序的主循環位於框架中,而使用類庫時,程序的主循環位於應用程序之中。框架會回調應用程序,而類庫則不會回調應用程序。ESFramework和StriveEngine中最主要的對象都以engine來命名,咱們也能夠看出框架對於程序主循環的控制——它會爲你把握方向、眼看前方、輕鬆駕馭!

·依賴注入(Dependency Injection)

  依賴注入與依賴倒置、控制反轉的關係仍舊是一本萬殊。依賴注入,就其廣義而言,便是經過「注入」的方式,來得到依賴。咱們知道,A對象依賴於B對象,等價於A對象內部存在對B對象的「調用」,而前提是A對象內部拿到了B對象的引用。B對象的引用的來源無非有如下幾種:A對象內部建立(不管是做爲字段仍是做爲臨時變量)、構造器注入、屬性注入、方法注入。後面三種方式統稱爲「依賴注入」,而第一種方式我也生造了一個名詞,稱爲「依賴內生」,兩者根本的差別即在於,我所依賴的對象的建立工做是否由我本身來完成。固然,這個是廣義的依賴注入的概念,而咱們通常不會這樣來使用。咱們一般使用的,是依賴注入的狹義的概念。不過,直接陳述其定義可能會過於詰屈聱牙,咱們仍是從具體的例子來看。

  好比OMCS網絡語音視頻框架,它實現了多媒體設備(麥克風、攝像頭、桌面、電子白板)的採集、編碼、網絡傳送、解碼、播放(或顯示)等相關的一整套流程,能夠快速地開發出視頻聊天系統、視頻會議系統、遠程醫療系統、遠程教育系統、網絡監控系統等等基於網絡多媒體的應用系統。然而,OMCS直接支持的是通用的語音視頻設備,而在某些系統中,須要使用網絡攝像頭或者特殊的視頻採集卡做爲視頻源,或者其它的聲音採集設備做爲音頻源,OMCS則提供了擴展接口——用戶本身實現這個擴展的接口,而後以「依賴注入」的方式將對象實例注入到OMCS中,從而完成對音、視頻設備的擴展。

「依賴注入」經常用於擴展,尤爲是在開發框架的設計中。從某種意義上來講,任何開發框架,天生都是不完整的應用程序。所以,一個優秀的開發框架,不只要讓開發者可以重用這些久經考驗的的卓越的解決方案,也要讓開發者可以向框架中插入自定義的業務邏輯,從而靈活自由地適應特定的業務場景的須要——也就是說要具有良好的可擴展性。好比上面提到的OMCS網絡語音視頻框架可應用於音、視頻聊天系統、視頻會議系統、遠程醫療系統、遠程教育系統、網絡監控系統等等基於網絡多媒體的應用系統;以及ESFramework通訊框架可以應用於即時通信系統,大型多人在線遊戲、在線網頁遊戲、文件傳送系統、數據採集系統、分佈式OA系統等任何須要分佈式通訊的軟件系統中——這種良好的擴展性都與「依賴注入」的使用密不可分!

·面向接口編程

談到最後,「面向接口編程」已是呼之欲出。不管是依賴倒置、控制反轉、仍是依賴注入,都已經蘊含着「面向接口編程」的思想。面向接口,就意味着面向抽象。做爲哲學範疇而言,規定性少稱爲抽象,規定性多稱爲具體。而接口,就是程序中的一種典型的「抽象」的形式。面向抽象,就意味着面向事物的本質規定性,擺脫感性雜多的牽絆,從而把握住「必然」——而這自己就意味着自由,由於自由就是對必然的認識。

也許以上的這段論述太過「哲學」,可是「一本之理」與「萬殊之理」自己就「體用不二」——總結來看,依賴倒置、控制反轉、依賴注入都圍繞着「解耦和」的問題,而同時自始至終又都是「面向接口編程」的方法——所以,「面向接口編程」天生就是「解耦和」的好辦法。由此也印證了從「抽象」到「自由」的這一段範疇的辯證衍化。

「面向對象」與「面向接口」並不是兩種不一樣的方法學,「面向接口」實際上是「面向對象」的內在要求,是其一部份內涵的集中表述。咱們對於理想軟件的期待常被歸納爲「高內聚,低耦合」,這也是整個現代軟件開發方法學所追求的目標。面向對象方法學做爲現代軟件開發方法學的表明,自己就蘊含着「高內聚,低耦合」的思想精髓,從這個意義上來講,「面向對象」這個表述更加側重於「高內聚」,「面向接口」的表述則更加側重於「低耦合」——不過是同一事物的不一樣側面罷了。

除此以外,咱們也能從「面向接口編程」的思想中獲得「世俗」的啓迪——《論語》裏面講,不患無位,患因此立;不患人之不己知,患其不能也——就是教導咱們要面向「我有沒有的本事?」、「我有沒有能力?」這樣的接口,而不是面向「我有沒有搞到位子?」、「別人了不瞭解我?」這樣的具體。依我看,這是莫大的教誨!

相關文章
相關標籤/搜索