說明:該文章轉(zhuǎn)自^知乎
作者:"在好"
那些年,空氣中仿佛還能聞到漢唐盛世的余韻氛改,因此你決不允許自己的臉上有油光稍刀,時刻保持活力撩独。然而敞曹,你一定曾為這些“高深術(shù)語”感到過困擾账月。也許時至今日,你仍對它們一知半解澳迫。不過就在今天局齿,這一切都將徹底改變!我將帶領(lǐng)你以一種全新的高清視角進入奇妙的編程世界橄登,領(lǐng)略涵泳在這些“高深術(shù)語”中的活潑潑的地氣抓歼,以及翩躚于青萍之末的云水禪心。
·內(nèi)聚
內(nèi)聚拢锹,通俗的來講谣妻,就是自己的東西自己保管,自己的事情自己做卒稳。
經(jīng)典理論告訴我們蹋半,程序的兩大要素:一個是數(shù)據(jù)(data),一個是操作(opration)充坑。而PASCAL之父NicklausWirth則進一步提出了“程序 = 數(shù)據(jù)結(jié)構(gòu) + 算法”的著名公式减江。雖然提法上有所差異,但是其根本內(nèi)涵卻是一致的捻爷,微妙的差別在于辈灼,“數(shù)據(jù) + 操作”是微觀的視域,“數(shù)據(jù)結(jié)構(gòu) + 算法”則是中觀的視域也榄。而在宏觀的視域下巡莹,我認為“程序 = 對象 + 消息”。對象是什么?對象就是保管好自己的東西,做好自己的事情的程序模塊——這就是內(nèi)聚!傳統(tǒng)的面向過程編程方法由于割裂了數(shù)據(jù)結(jié)構(gòu)和算法蔑穴,使得軟件的內(nèi)聚性普遍低迷屁置,曾一度引發(fā)了軟件危機。試想鹅巍,大家都自己的東西不好好保管,自己的事情也不好好做,不引發(fā)危機才怪呢贸营!當然,對象的內(nèi)聚只是內(nèi)聚的一個層次岩睁,在不同的尺度下其實都有內(nèi)聚的要求钞脂,比如方法也要講內(nèi)聚,架構(gòu)也要講內(nèi)聚捕儒。
《周易·彖傳》中講“乾道變化冰啃,各正性命,保合太和刘莹,乃利貞”阎毅,就是要求每一個個體因循著各自的稟賦而努力成就各自的品性,然后各自保全点弯,彼此和合扇调,最終達成宇宙的完滿狀態(tài)∏栏兀《論語·憲問》中狼钮,子路問君子。子曰:“修己以敬捡絮“疚撸”曰:“如斯而已乎?”曰:“修己以安人”福稳,更是明確的教導(dǎo)我們要不斷提高自身的內(nèi)聚性涎拉,最大限度地減少給他人造成的麻煩,從而達到安人灵寺、安百姓曼库、安天下的目標。我想略板,成長的過程就是一個不斷提升內(nèi)聚的過程毁枯。“自己的東西自己保管叮称,自己的事情自己做”种玛,這些孩提時代的教誨藐鹤,放到今天仍能讓不少“大人”臉紅不已。太多的人保管不好自己的“東西”赂韵,保管不好自己的身體娱节,保管不好自己的婚姻,更保管不好自己如蛛絲般震顫飄蕩的狂亂的心祭示。至于做好自己的事情肄满,則更是惘然,甚至很多人連自己的事情是什么都搞不清楚质涛,因此渾渾噩噩稠歉,飽食終日。內(nèi)聚汇陆,是一個值得我們好好反思的問題怒炸。
·依賴·耦合
在面向?qū)ο缶幊讨校瑢ο笞陨硎莾?nèi)聚的毡代,是保管好自己的數(shù)據(jù)阅羹,完成好自己的操作的,而對外界呈現(xiàn)出自己的狀態(tài)和行為教寂。但是捏鱼,沒有絕對的自力更生,對外開放也是必要的孝宗!一個對象穷躁,往往需要跟其他對象打交道耕肩,既包括獲知其他對象的狀態(tài)因妇,也包括仰賴其他對象的行為,而一旦這樣的事情發(fā)生時猿诸,我們便稱該對象依賴于另一對象婚被。只要兩個對象之間存在一方依賴一方的關(guān)系,那么我們就稱這兩個對象之間存在耦合梳虽。比如媽媽和baby址芯,媽媽要隨時關(guān)注baby的睡、醒窜觉、困谷炸、哭、尿等等狀態(tài)禀挫,baby則要仰賴媽媽的喂奶旬陡、哄睡、換紙尿褲等行為语婴,從程序的意義上說描孟,二者互相依賴驶睦,因此也存在耦合。首先要說匿醒,耦合是必要的场航。我們來看以下這個實驗。
【王陽明與山中之花】
classFlowersInMountain
{publicboolIsBloomed//盛開狀態(tài){get{returntrue; }
}
}classWangYangMing
{//////王陽明會稽山賞花//////山花盛開與否publicboolAdmireFlowers()
{return?;
}
}staticvoidMain()
{
WangYangMing wym=newWangYangMing();
FlowersInMountain flower=newFlowersInMountain();if(wym.AdmireFlowers())
{
MessageBox.Show("我看見山花開放廉羔!");
}else{
MessageBox.Show("我看見山花未開溉痢!");
}
}
View Code
由于王陽明這個對象不依賴山花這個對象,又沒有其他的方式來獲知山花的盛開狀態(tài)憋他,所以他要么選擇不說适室,要么瞎說,但不說編譯是通不過举瑰,而瞎說作為王陽明來講也是通不過的捣辆,所以這個系統(tǒng)是無法成立的。要想系統(tǒng)成立此迅,必須要這樣寫:
publicboolAdmireFlowers()
{returnflower.IsBloomed; ;? ? ? ? }
無論這個山花對象是怎么來的汽畴,作為參數(shù)傳入還是作為屬性設(shè)置、還是在內(nèi)部構(gòu)造出來耸序,總之忍些,王陽明與山花之間發(fā)生了依賴,二者之間產(chǎn)生了耦合坎怪。當然罢坝,這是一個很淺顯的問題。有趣的是王陽明對此事的看法:“你未看花時搅窿,花與你同寂嘁酿;你來看花,花于你則一時分明起來男应∧炙荆可見心外無物!”王陽明講的是對的沐飘!“心外無物”翻譯技術(shù)語言是這樣的:不存在耦合的兩個對象必然拿不到對方的引用游桩!
·耦合度·解耦和
耦合的程度就是耦合度,也就是雙方依賴的程度耐朴。上文所說的媽媽和baby就是強耦合借卧。而你跟快遞小哥之間則是弱耦合。一般來說耦合度過高并不是一件好事筛峭。就拿作為IT精英的你來說吧铐刘,上級隨時敦促你的工作進度,新手頻繁地需要你指導(dǎo)問題蜒滩,隔三差五還需要參加酒局飯局滨达,然后還要天天看領(lǐng)導(dǎo)的臉色奶稠、關(guān)注老婆的心情,然后你還要關(guān)注代碼中的bug捡遍、bug锌订、bug,和需求的變化画株、變化辆飘、變化,都夠焦頭爛額了谓传,還猝不及防的要關(guān)注眼睛蜈项、頸椎、前列腺和頭發(fā)的狀態(tài)续挟,然后你再炒個股紧卒,這些加起來大概就是個強耦合了。從某種意義上來說诗祸,耦合天生就與自由為敵跑芳,無論是其他對象依賴于你,還是你依賴其他對象直颅。比如有人嗜煙博个、酗酒,你有多依賴它們就有多不自由功偿;比如有人家里生了七八個娃盆佣,還有年邁的父母、岳父母械荷,他們有多依賴你共耍,你就有多不自由。所以老子這樣講:“五音令人耳聾养葵,五色令人目盲征堪,馳騁狩獵令人心發(fā)狂,難得之貨令人行妨关拒。”盧梭也是不無悲涼的說“人生而自由庸娱,卻又無往而不在枷鎖中”着绊。因此,要想自由熟尉,就必須要降低耦合归露,而這個過程就叫做解耦和。
·依賴倒置(Dependence Inversion Principle)
解耦和最重要的原則就是依賴倒置原則:
高層模塊不應(yīng)該依賴底層模塊斤儿,他們都應(yīng)該依賴抽象剧包。抽象不應(yīng)該依賴于細節(jié)恐锦,細節(jié)應(yīng)該依賴于抽象。
《資本論》中都曾闡釋依賴倒轉(zhuǎn)原則——在商品經(jīng)濟的萌芽時期疆液,出現(xiàn)了物物交換一铅。假設(shè)你要買一個IPhone,賣IPhone的老板讓你拿一頭豬跟他換堕油,可是你并沒有養(yǎng)豬潘飘,你只會編程。所以你找到一位養(yǎng)豬戶掉缺,說給他做一個養(yǎng)豬的APP來換他一頭豬卜录,他說換豬可以,但是得用一條金項鏈來換——所以這里就出現(xiàn)了一連串的對象依賴眶明,從而造成了嚴重的耦合災(zāi)難艰毒。解決這個問題的最好的辦法就是,買賣雙發(fā)都依賴于抽象——也就是貨幣——來進行交換搜囱,這樣一來耦合度就大為降低了现喳。
再舉一個編程中的依賴倒置的例子。我們知道犬辰,在通信中嗦篱,消息的收發(fā)和消息的處理往往密不可分。就一般的通信框架而言幌缝,消息的收發(fā)通常是已經(jīng)實現(xiàn)了的灸促,而消息的處理則是需要用戶來自定義完成的。先看一個正向依賴的例子:輕量級通信引擎StriveEngine涵卵。tcpServerEngine是StriveEngine.dll提供通信引擎浴栽,它發(fā)布有一個MessageReceived事件。假設(shè)我定義了一個CustomizeHandler類來用于消息處理轿偎,那么CustomizeHandler的內(nèi)部需要預(yù)定tcpServerEngine的MessageReceived事件典鸡,因此customizeHandler依賴于tcpServerEngine,這就是一個普通的依賴關(guān)系坏晦,也就是高層模塊依賴于低層模塊萝玷。
而ESFramework通信框架則應(yīng)用了依賴倒轉(zhuǎn)原則。ESFramework定義了一個IcustomizeHandler接口昆婿,用戶在進行消息處理時球碉,實現(xiàn)該接口,然后將其注入到rapidPassiveEngine客戶端通信引擎之中仓蛆。
classCustomizeHandler: ICustomizeHandler
{publicvoidHandleInformation(stringsourceUserID,intinformationType,byte[] info)
{
······
}publicbyte[] HandleQuery(stringsourceUserID,intinformationType,byte[] info)
{
······
}
}
IRapidPassiveEngine rapidPassiveEngine=ESPlus.Rapid.RapidEngineFactory.CreatePassiveEngine();
CustomizeHandler customizeHandler=newCustomizeHandler();
rapidPassiveEngine.Initialize("ID","passWord","127.0.0.1",9000, customizeHandler);
View Code
很明顯睁冬,相比于上一個例子,這里的依賴關(guān)系變成了rapidPassiveEngine依賴于customizeHandler看疙,也就是說依賴關(guān)系倒置了過來豆拨,上層模塊不再依賴于底層模塊直奋,而是它們共同依賴于抽象。rapidPassiveEngine依賴的是IcustomizeHandler接口類型的參數(shù)施禾,customizeHandler同樣是以實現(xiàn)的接口的方式依賴于IcustomizeHandler——這就是一個依賴倒置的典范脚线。
·控制反轉(zhuǎn)(Inversion of Control)
控制反轉(zhuǎn)跟依賴倒置是如出一轍的兩個概念,當存在依賴倒置的時候往往也存在著控制反轉(zhuǎn)拾积。但是控制反轉(zhuǎn)也有自己的獨特內(nèi)涵殉挽。
首先我們要區(qū)分兩個角色,server 跟 Client拓巧,也就是服務(wù)方和客戶方斯碌。提供服務(wù)端的一方稱為服務(wù)方,請求服務(wù)的一方稱為客戶方肛度。我們最熟悉的例子就是分布式應(yīng)用的C/S架構(gòu)傻唾,服務(wù)端和客戶端。其實除此之外承耿,C/S關(guān)系處處可見冠骄。比如在TCP/IP協(xié)議棧中,我們知道加袋,每層協(xié)議為上一層提供服務(wù)凛辣,那么這里就是一個C/S關(guān)系。當我們使用開發(fā)框架時职烧,開發(fā)框架就是作為服務(wù)方扁誓,而我們自己編寫的業(yè)務(wù)應(yīng)用就是客戶方。當Client調(diào)用server時蚀之,這個叫做一般的控制蝗敢;而當server調(diào)用Client時,就是我們所說的控制反轉(zhuǎn)足删,同時我們也將這個調(diào)用稱為“回調(diào)”寿谴。控制反轉(zhuǎn)跟依賴倒置都是一種編程思想失受,依賴倒置著眼于調(diào)用的形式讶泰,而控制反轉(zhuǎn)則著眼于程序流程的控制權(quán)。一般來說贱纠,程序的控制權(quán)屬于Client峻厚,而一旦控制權(quán)交到server,就叫控制反轉(zhuǎn)谆焊。比如你去下館子,你是Client餐館是server浦夷。你點菜辖试,餐館負責做菜辜王,程序流程的控制權(quán)屬于Client;而如果你去自助餐廳罐孝,程序流程的控制權(quán)就轉(zhuǎn)到server了呐馆,也就是控制反轉(zhuǎn)。
控制反轉(zhuǎn)的思想體現(xiàn)在諸多領(lǐng)域莲兢。比如事件的發(fā)布/ 訂閱就是一種控制反轉(zhuǎn)汹来,GOF設(shè)計模式中也多處體現(xiàn)了控制反轉(zhuǎn),比如典型的模板方法模式等改艇。而開發(fā)框架則是控制反轉(zhuǎn)思想應(yīng)用的集中體現(xiàn)收班。比如之前所舉的ESFramework通信框架的例子,通信引擎回調(diào)用戶自定義的消息處理器谒兄,這就是一個控制反轉(zhuǎn)摔桦。以及ESFramework回調(diào)用戶自定義的群組關(guān)系和好友關(guān)系,回調(diào)用戶自定義的用戶管理器以管理在線用戶相關(guān)狀態(tài)承疲,回調(diào)用戶自定義的登陸驗證處理邻耕,等等不一而足。再比如與ESFramework一脈相承的輕量級通信引擎StriveEngine燕鸽,通過回調(diào)用戶自定義的通信協(xié)議來實現(xiàn)更加靈活的通信兄世。
由此我們也可以總結(jié)出開發(fā)框架與類庫的區(qū)別:使用開發(fā)框架時,框架掌握程序流程的控制權(quán)啊研,而使用類庫時御滩,則是應(yīng)用程序掌握程序流程的控制權(quán)”妫或者說艾恼,使用框架時,程序的主循環(huán)位于框架中麸锉,而使用類庫時钠绍,程序的主循環(huán)位于應(yīng)用程序之中』ǔ粒框架會回調(diào)應(yīng)用程序柳爽,而類庫則不會回調(diào)應(yīng)用程序。ESFramework和StriveEngine中最主要的對象都以engine來命名碱屁,我們也可以看出框架對于程序主循環(huán)的控制——它會為你把握方向磷脯、眼看前方、輕松駕馭娩脾!
·依賴注入(Dependency Injection)
依賴注入與依賴倒置赵誓、控制反轉(zhuǎn)的關(guān)系仍舊是一本萬殊。依賴注入,就其廣義而言俩功,即是通過“注入”的方式幻枉,來獲得依賴。我們知道诡蜓,A對象依賴于B對象熬甫,等價于A對象內(nèi)部存在對B對象的“調(diào)用”,而前提是A對象內(nèi)部拿到了B對象的引用蔓罚。B對象的引用的來源無非有以下幾種:A對象內(nèi)部創(chuàng)建(無論是作為字段還是作為臨時變量)椿肩、構(gòu)造器注入、屬性注入豺谈、方法注入郑象。后面三種方式統(tǒng)稱為“依賴注入”,而第一種方式我也生造了一個名詞核无,稱為“依賴內(nèi)生”扣唱,二者根本的差異即在于,我所依賴的對象的創(chuàng)建工作是否由我自己來完成团南。當然噪沙,這個是廣義的依賴注入的概念,而我們一般不會這樣來使用吐根。我們通常使用的正歼,是依賴注入的狹義的概念。不過拷橘,直接陳述其定義可能會過于詰屈聱牙局义,我們還是從具體的例子來看。
比如OMCS網(wǎng)絡(luò)語音視頻框架冗疮,它實現(xiàn)了多媒體設(shè)備(麥克風萄唇、攝像頭、桌面术幔、電子白板)的采集另萤、編碼、網(wǎng)絡(luò)傳送诅挑、解碼四敞、播放(或顯示)等相關(guān)的一整套流程,可以快速地開發(fā)出視頻聊天系統(tǒng)拔妥、視頻會議系統(tǒng)忿危、遠程醫(yī)療系統(tǒng)、遠程教育系統(tǒng)没龙、網(wǎng)絡(luò)監(jiān)控系統(tǒng)等等基于網(wǎng)絡(luò)多媒體的應(yīng)用系統(tǒng)铺厨。然而缎玫,OMCS直接支持的是通用的語音視頻設(shè)備,而在某些系統(tǒng)中努释,需要使用網(wǎng)絡(luò)攝像頭或者特殊的視頻采集卡作為視頻源碘梢,或者其它的聲音采集設(shè)備作為音頻源咬摇,OMCS則提供了擴展接口——用戶自己實現(xiàn)這個擴展的接口伐蒂,然后以“依賴注入”的方式將對象實例注入到OMCS中,從而完成對音肛鹏、視頻設(shè)備的擴展逸邦。擴展方法詳情參考
“依賴注入”常常用于擴展,尤其是在開發(fā)框架的設(shè)計中在扰。從某種意義上來說缕减,任何開發(fā)框架,天生都是不完整的應(yīng)用程序芒珠。因此桥狡,一個優(yōu)秀的開發(fā)框架,不僅要讓開發(fā)者能夠重用這些久經(jīng)考驗的的卓越的解決方案皱卓,也要讓開發(fā)者能夠向框架中插入自定義的業(yè)務(wù)邏輯裹芝,從而靈活自由地適應(yīng)特定的業(yè)務(wù)場景的需要——也就是說要具備良好的可擴展性。比如上面提到的OMCS網(wǎng)絡(luò)語音視頻框架可應(yīng)用于音娜汁、視頻聊天系統(tǒng)嫂易、視頻會議系統(tǒng)、遠程醫(yī)療系統(tǒng)掐禁、遠程教育系統(tǒng)怜械、網(wǎng)絡(luò)監(jiān)控系統(tǒng)等等基于網(wǎng)絡(luò)多媒體的應(yīng)用系統(tǒng);以及ESFramework通信框架能夠應(yīng)用于即時通訊系統(tǒng)傅事,大型多人在線游戲缕允、在線網(wǎng)頁游戲、文件傳送系統(tǒng)蹭越、數(shù)據(jù)采集系統(tǒng)障本、分布式OA系統(tǒng)等任何需要分布式通信的軟件系統(tǒng)中——這種良好的擴展性都與“依賴注入”的使用密不可分!
·面向接口編程
談到最后般又,“面向接口編程”已經(jīng)是呼之欲出彼绷。無論是依賴倒置、控制反轉(zhuǎn)茴迁、還是依賴注入寄悯,都已經(jīng)蘊含著“面向接口編程”的思想。面向接口堕义,就意味著面向抽象猜旬。作為哲學范疇而言脆栋,規(guī)定性少稱為抽象,規(guī)定性多稱為具體洒擦。而接口椿争,就是程序中的一種典型的“抽象”的形式。面向抽象熟嫩,就意味著面向事物的本質(zhì)規(guī)定性秦踪,擺脫感性雜多的牽絆,從而把握住“必然”——而這本身就意味著自由掸茅,因為自由就是對必然的認識椅邓。
也許以上的這段論述太過“哲學”,但是“一本之理”與“萬殊之理”本身就“體用不二”——總結(jié)來看昧狮,依賴倒置景馁、控制反轉(zhuǎn)、依賴注入都圍繞著“解耦和”的問題逗鸣,而同時自始至終又都是“面向接口編程”的方法——因此合住,“面向接口編程”天生就是“解耦和”的好辦法。由此也印證了從“抽象”到“自由”的這一段范疇的辯證衍化撒璧。
“面向?qū)ο蟆迸c“面向接口”并非兩種不同的方法學透葛,“面向接口”其實是“面向?qū)ο蟆钡膬?nèi)在要求,是其一部分內(nèi)涵的集中表述沪悲。我們對于理想軟件的期待常被概括為“高內(nèi)聚获洲,低耦合”,這也是整個現(xiàn)代軟件開發(fā)方法學所追求的目標殿如。面向?qū)ο蠓椒▽W作為現(xiàn)代軟件開發(fā)方法學的代表贡珊,本身就蘊含著“高內(nèi)聚,低耦合”的思想精髓涉馁,從這個意義上來說门岔,“面向?qū)ο蟆边@個表述更加側(cè)重于“高內(nèi)聚”,“面向接口”的表述則更加側(cè)重于“低耦合”——不過是同一事物的不同側(cè)面罷了烤送。
除此之外寒随,我們也能從“面向接口編程”的思想中得到“世俗”的啟迪——《論語》里面講,不患無位帮坚,患所以立妻往;不患人之不己知,患其不能也——就是教導(dǎo)我們要面向“我有沒有的本事试和?”讯泣、“我有沒有能力?”這樣的接口阅悍,而不是面向“我有沒有搞到位子好渠?”昨稼、“別人了不了解我?”這樣的具體拳锚。依我看假栓,這是莫大的教誨!