依賴倒置?控制反轉?依賴注入?面向接口編程

·內聚

內聚,通俗的來講,就是自己的東西自己保管,自己的事情自己做。

經典理論告訴我們,程序的兩大要素:一個是數據(

data

),一個是操作(

opration

)。而

PASCAL

之父

Nicklaus

Wirth

則進一步提出了“程序 = 數據結構 + 算法”的著名公式。雖然提法上有所差異,但是其根本內涵卻是一致的,微妙的差別在于,“數據 + 操作”是微觀的視域,“數據結構 + 算法”則是中觀的視域。而在宏觀的視域下,我認為“程序 = 對象 + 消息”。對象是什么?對象就是保管好自己的東西,做好自己的事情的程序模塊——這就是內聚!傳統的面向過程編程方法由于割裂了數據結構和算法,使得軟件的內聚性普遍低迷,曾一度引發了軟件危機。試想,大家都自己的東西不好好保管,自己的事情也不好好做,不引發危機才怪呢!當然,對象的內聚只是內聚的一個層次,在不同的尺度下其實都有內聚的要求,比如方法也要講內聚,架構也要講內聚。

《周易·彖傳》中講“乾道變化,各正性命,保合太和,乃利貞”,就是要求每一個個體因循著各自的稟賦而努力成就各自的品性,然后各自保全,彼此和合,最終達成宇宙的完滿狀態。《論語·憲問》中,子路問君子。子曰:“修己以敬?!痹唬骸叭缢苟押酰俊痹唬骸靶藜阂园踩恕保敲鞔_的教導我們要不斷提高自身的內聚性,最大限度地減少給他人造成的麻煩,從而達到安人、安百姓、安天下的目標。我想,成長的過程就是一個不斷提升內聚的過程?!白约旱臇|西自己保管,自己的事情自己做”,這些孩提時代的教誨,放到今天仍能讓不少“大人”臉紅不已。太多的人保管不好自己的“東西”,保管不好自己的身體,保管不好自己的婚姻,更保管不好自己如蛛絲般震顫飄蕩的狂亂的心。至于做好自己的事情,則更是惘然,甚至很多人連自己的事情是什么都搞不清楚,因此渾渾噩噩,飽食終日。內聚,是一個值得我們好好反思的問題。

·依賴·耦合

在面向對象編程中,對象自身是內聚的,是保管好自己的數據,完成好自己的操作的,而對外界呈現出自己的狀態和行為。但是,沒有絕對的自力更生,對外開放也是必要的!一個對象,往往需要跟其他對象打交道,既包括獲知其他對象的狀態,也包括仰賴其他對象的行為,而一旦這樣的事情發生時,我們便稱該對象依賴于另一對象。只要兩個對象之間存在一方依賴一方的關系,那么我們就稱這兩個對象之間存在耦合。

比如媽媽和baby,媽媽要隨時關注baby的睡、醒、困、哭、尿等等狀態,baby則要仰賴媽媽的喂奶、哄睡、換紙尿褲等行為,從程序的意義上說,二者互相依賴,因此也存在耦合。首先要說,耦合是必要的。我們來看以下這個實驗。

【王陽明與山中之花

image

View Code

由于王陽明這個對象不依賴山花這個對象,又沒有其他的方式來獲知山花的盛開狀態,所以他要么選擇不說,要么瞎說,但不說編譯是通不過,而瞎說作為王陽明來講也是通不過的,所以這個系統是無法成立的。要想系統成立,必須要這樣寫:

<pre style="margin: 0px; padding: 0px; white-space: pre-wrap; word-wrap: break-word; font-family: "Courier New" !important; font-size: 12px !important;">

public

bool

AdmireFlowers()
{

return

flower .IsBloomed; ;
}

</pre>

無論這個山花對象是怎么來的,作為參數傳入還是作為屬性設置、還是在內部構造出來,總之,王陽明與山花之間發生了依賴,二者之間產生了耦合。

當然,這是一個很淺顯的問題。有趣的是王陽明對此事的看法:“你未看花時,花與你同寂;你來看花,花于你則一時分明起來??梢娦耐鉄o物!”王陽明講的是對的!“心外無物”翻譯技術語言是這樣的:不存在耦合的兩個對象必然拿不到對方的引用!

·耦合度·解耦和

耦合的程度就是耦合度,也就是雙方依賴的程度。上文所說的媽媽和

baby

就是強耦合。而你跟快遞小哥之間則是弱耦合。一般來說耦合度過高并不是一件好事。就拿作為IT精英的你來說吧,上級隨時敦促你的工作進度,新手頻繁地需要你指導問題,隔三差五還需要參加酒局飯局,然后還要天天看領導的臉色、關注老婆的心情,然后你還要關注代碼中的

bug

bug

、

bug

,和需求的變化、變化、變化,都夠焦頭爛額了,還猝不及防的要關注眼睛、頸椎、前列腺和頭發的狀態,然后你再炒個股,這些加起來大概就是個強耦合了。從某種意義上來說,耦合天生就與自由為敵,無論是其他對象依賴于你,還是你依賴其他對象。比如有人嗜煙、酗酒,你有多依賴它們就有多不自由;比如有人家里生了七八個娃,還有年邁的父母、岳父母,他們有多依賴你,你就有多不自由。所以老子這樣講:“五音令人耳聾,五色令人目盲,馳騁狩獵令人心發狂,難得之貨令人行妨?!北R梭也是不無悲涼的說“人生而自由,卻又無往而不在枷鎖中”。

因此,要想自由,就必須要降低耦合,而這個過程就叫做解耦和。

image

·依賴倒置(Dependence Inversion Principle)

解耦和最重要的原則就是依賴倒置原則:

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

《資本論》中都曾闡釋依賴倒轉原則——在商品經濟的萌芽時期,出現了物物交換。假設你要買一個

IPhone

,賣

IPhone

的老板讓你拿一頭豬跟他換,可是你并沒有養豬,你只會編程。所以你找到一位養豬戶,說給他做一個養豬的APP來換他一頭豬,他說換豬可以,但是得用一條金項鏈來換——所以這里就出現了一連串的對象依賴,從而造成了嚴重的耦合災難。解決這個問題的最好的辦法就是,買賣雙發都依賴于抽象——也就是貨幣——來進行交換,這樣一來耦合度就大為降低了。

再舉一個編程中的依賴倒置的例子。我們知道,在通信中,消息的收發和消息的處理往往密不可分。就一般的通信框架而言,消息的收發通常是已經實現了的,而消息的處理則是需要用戶來自定義完成的。先看一個正向依賴的例子:

輕量級通信引擎

StriveEngine

。tcpServerEngine是StriveEngine.dll提供通信引擎,它發布有一個MessageReceived事件。假設我定義了一個CustomizeHandler類來用于消息處理,那么CustomizeHandler的內部需要預定tcpServerEngine的MessageReceived事件,因此customizeHandler依賴于tcpServerEngine,這就是一個普通的依賴關系,也就是高層模塊依賴于低層模塊。

image
            ![image](http://upload-images.jianshu.io/upload_images/943788-e3f9978596869edd.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

 而 

[ESFramework

通信框架](http://www.cnblogs.com/zhuweisky/archive/2010/08/12/1798211.html)

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

image

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時,就是我們所說的控制反轉,同時我們也將這個調用稱為“回調”??刂品崔D跟依賴倒置都是一種編程思想,依賴倒置著眼于調用的形式,而控制反轉則著眼于程序流程的控制權。一般來說,程序的控制權屬于Client,而一旦控制權交到server,就叫控制反轉。比如你去下館子,你是Client餐館是server。你點菜,餐館負責做菜,程序流程的控制權屬于Client;而如果你去自助餐廳,程序流程的控制權就轉到server了,也就是控制反轉。

  控制反轉的思想體現在諸多領域。比如事件的發布/ 訂閱就是一種控制反轉,GOF設計模式中也多處體現了控制反轉,比如典型的模板方法模式等。而開發框架則是控制反轉思想應用的集中體現。比如之前所舉的 [ESFramework

通信框架](http://www.cnblogs.com/zhuweisky/archive/2010/08/12/1798211.html) 的例子,通信引擎回調用戶自定義的消息處理器,這就是一個控制反轉。以及ESFramework回調用戶自定義的群組關系和好友關系,回調用戶自定義的用戶管理器以管理在線用戶相關狀態,回調用戶自定義的登陸驗證處理,等等不一而足。再比如與ESFramework一脈相承的 [輕量級通信引擎

StriveEngine](http://blog.oraycn.com/StriveEngine.aspx) ,通過回調用戶自定義的通信協議來實現更加靈活的通信。

image
  由此我們也可以總結出開發框架與類庫的區別:使用開發框架時,框架掌握程序流程的控制權,而使用類庫時,則是應用程序掌握程序流程的控制權?;蛘哒f,使用框架時,程序的主循環位于框架中,而使用類庫時,程序的主循環位于應用程序之中??蚣軙卣{應用程序,而類庫則不會回調應用程序。ESFramework和StriveEngine中最主要的對象都以engine來命名,我們也可以看出框架對于程序主循環的控制——它會為你把握方向、眼看前方、輕松駕馭! 

·依賴注入(Dependency Injection

)

依賴注入與依賴倒置、控制反轉的關系仍舊是一本萬殊。依賴注入,就其廣義而言,即是通過“注入”的方式,來獲得依賴。我們知道,A對象依賴于B對象,等價于A對象內部存在對B對象的“調用”,而前提是A對象內部拿到了B對象的引用。B對象的引用的來源無非有以下幾種:A對象內部創建(無論是作為字段還是作為臨時變量)、構造器注入、屬性注入、方法注入。后面三種方式統稱為“依賴注入”,而第一種方式我也生造了一個名詞,稱為“依賴內生”,二者根本的差異即在于,我所依賴的對象的創建工作是否由我自己來完成。當然,這個是廣義的依賴注入的概念,而我們一般不會這樣來使用。我們通常使用的,是依賴注入的狹義的概念。不過,直接陳述其定義可能會過于詰屈聱牙,我們還是從具體的例子來看。

[圖片上傳中...(image-e310f6-1511840754597-0)]

比如

OMCS網絡語音視頻框架

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

詳情參考](http://www.cnblogs.com/zhuweisky/p/3651229.html)

  “依賴注入”常常用于擴展,尤其是在開發框架的設計中。從某種意義上來說,任何開發框架,天生都是不完整的應用程序。因此,一個優秀的開發框架,不僅要讓開發者能夠重用這些久經考驗的的卓越的解決方案,也要讓開發者能夠向框架中插入自定義的業務邏輯,從而靈活自由地適應特定的業務場景的需要——也就是說要具備良好的可擴展性。比如上面提到的OMCS網絡語音視頻框架可應用于音、視頻聊天系統、視頻會議系統、遠程醫療系統、遠程教育系統、網絡監控系統等等基于網絡多媒體的應用系統;以及 

ESFramework通信框架

能夠應用于即時通訊系統,大型多人在線游戲、在線網頁游戲、文件傳送系統、數據采集系統、分布式OA系統等任何需要分布式通信的軟件系統中——這種良好的擴展性都與“依賴注入”的使用密不可分!

·面向接口編程

  談到最后,“面向接口編程”已經是呼之欲出。無論是依賴倒置、控制反轉、還是依賴注入,都已經蘊含著“面向接口編程”的思想。面向接口,就意味著面向抽象。作為哲學范疇而言,規定性少稱為抽象,規定性多稱為具體。而接口,就是程序中的一種典型的“抽象”的形式。面向抽象,就意味著面向事物的本質規定性,擺脫感性雜多的牽絆,從而把握住“必然”——而這本身就意味著自由,因為自由就是對必然的認識。

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

“面向對象”與“面向接口”并非兩種不同的方法學,“面向接口”其實是“面向對象”的內在要求,是其一部分內涵的集中表述。我們對于理想軟件的期待常被概括為“高內聚,低耦合”,這也是整個現代軟件開發方法學所追求的目標。面向對象方法學作為現代軟件開發方法學的代表,本身就蘊含著“高內聚,低耦合”的思想精髓,從這個意義上來說,“面向對象”這個表述更加側重于“高內聚”,“面向接口”的表述則更加側重于“低耦合”——不過是同一事物的不同側面罷了。

  除此之外,我們也能從“面向接口編程”的思想中得到“世俗”的啟迪——《論語》里面講,不患無位,患所以立;不患人之不己知,患其不能也——就是教導我們要面向“我有沒有的本事?”、“我有沒有能力?”這樣的接口,而不是面向“我有沒有搞到位子?”、“別人了不了解我?”這樣的具體。依我看,這是莫大的教誨!
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,739評論 6 534
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,634評論 3 419
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,653評論 0 377
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,063評論 1 314
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,835評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,235評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,315評論 3 442
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,459評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,000評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,819評論 3 355
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,004評論 1 370
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,560評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,257評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,676評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,937評論 1 288
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,717評論 3 393
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,003評論 2 374

推薦閱讀更多精彩內容