- Live軟件開發(fā)面面談
- 潘俊編著
- 3820字
- 2019-07-30 17:55:01
1.6 有必要針對接口編程嗎
到現(xiàn)在為止,我們講的都是針對接口編程的意義、消除依賴的實現(xiàn)方式,仿佛針對接口編程是一個先驗的、放之四海而皆準的真理。市面上介紹編程的教程和文章、網(wǎng)絡(luò)上分享經(jīng)驗的博客和帖子,無論是不是關(guān)于針對接口編程的主題,有許多在代碼樣例中每創(chuàng)建一個類前都先定義一個接口(在后面的討論中不妨簡稱為接口先行),而且在文字中透露出這樣做不言而喻的正確性。這時候,人們天生的懷疑精神又有用武之地了。有必要在一切場合都針對接口編程嗎?或者更準確地說,什么情況下應(yīng)該針對接口編程(什么情況下不需要、不應(yīng)該)?
1.6.1 針對接口編程的成本
本章先前所論述的針對接口編程的好處都是真實的,與此同時還有一點也是真實的,就是針對接口編程的成本。不這么做時,調(diào)用者直接創(chuàng)建被調(diào)用者的實例,一行代碼足矣。這么做時,先要抽象出被調(diào)用者的接口,讓具體類型實現(xiàn)該接口,然后采用工廠、服務(wù)定位器或依賴注入的模式,還要設(shè)定配置文件、慣例或元數(shù)據(jù),多寫無數(shù)行代碼。我們?nèi)粘YI東西的時候會講究性價比,寫程序時自然也要考慮這樣做值不值得。如果為每個類都定義一個接口,一個項目里的代碼文件數(shù)量就幾乎要翻一倍。而且上述接口先行的樣例往往是定義完接口就了事,針對接口編程所需的配套工作都略而不談,讓這些代碼表面上既用到了接口,又不甚煩瑣。實際上假若真將這種接口先行的方式貫徹到項目開發(fā)中,任何人也堅持不了——為被調(diào)用者創(chuàng)建了接口,那調(diào)用者要不要也有接口,工廠、服務(wù)定位器和依賴注入的容器要不要創(chuàng)建接口,解析配置文件和元數(shù)據(jù)的對象要不要接口,這些對象用到的任何一個哪怕是輔助的提供方便的對象要不要接口……項目代碼的主體將變成實現(xiàn)針對接口編程的腳手架(Scaffold),業(yè)務(wù)邏輯反將退居其次。所以無論多么主張接口先行,最后要面對的問題都是什么情況下需要為類創(chuàng)建接口并針對接口編程?
1.6.2 接口的意義
針對接口編程是手段,目的是消除依賴。本來調(diào)用者使用被調(diào)用者的類型,針對接口編程后,調(diào)用者使用被調(diào)用者遵循的接口。為什么使用類型是依賴,使用接口就消除了依賴呢?如果說在前一種情況中調(diào)用者依賴了被調(diào)用者的類型,那么后一種情況為什么不說調(diào)用者依賴了被調(diào)用者的接口?可以從兩個角度回答這個疑問。
首先,可以說“被調(diào)用者的類型”,即該類型是屬于被調(diào)用者的;但嚴格地講,我們不能說“被調(diào)用者的接口”,因為該接口不屬于被調(diào)用者,同樣也不屬于調(diào)用者。對于需要合作的調(diào)用者和被調(diào)用者雙方,接口是第三方的中介。雖然有時把名稱上體現(xiàn)被調(diào)用者共同點的接口看成是它們的代表(如ICodec代表MP3Codec),又或者反過來把接口看作調(diào)用者對被調(diào)用者需要的功能的抽象,但實際上就像1.2節(jié)中最后的圖例所示,接口是獨立于調(diào)用者和被調(diào)用者的。所以將調(diào)用者使用的被調(diào)用者的具體類型換成接口后,調(diào)用者就不再依賴被調(diào)用者了,那么對第三方接口的引用算不算依賴呢?
其次,接口本質(zhì)上也是類型,和普通類型的差別就在于它不包含具體的實現(xiàn)。一個概念的內(nèi)涵越大,外延就越小;反之,內(nèi)涵越小,外延就越大。用另一種方式來表述就是,一個對象越具體,應(yīng)用范圍就越小,有效時間越短,越容易發(fā)生變化;反之,一個對象越抽象,應(yīng)用范圍就越廣,越穩(wěn)定。【注:與之相應(yīng)的是,越具體的對象在它的小范圍內(nèi)發(fā)揮的作用越大,越直接;越抽象的對象在它的大范圍內(nèi)發(fā)揮的作用越小,越間接。這個哲學(xué)的陳述有各種場景的應(yīng)用(因為它本身就很抽象)。比如說對他人的關(guān)心,我常常發(fā)現(xiàn)一個人關(guān)心的人越少,感情就越濃烈,像一些溺愛子孫的父母和老人,大部分心思都用在孩子身上,吃穿用住具體到無以復(fù)加;而那些關(guān)心社會大眾胸懷天下的大人物,對身邊的人關(guān)心的強度卻不怎么高。盧梭關(guān)心人類社會不公正的來源,寫《社會契約論》和《愛彌兒》,對人類的愛很普遍、很抽象,自己的孩子卻生一個拋棄一個。很多哲學(xué)家視婚姻為累贅。西方社會的人際關(guān)系和中國相比,正是親人不親,外人不外。】接口因為不包含具體的實現(xiàn),是最抽象的,因而與實現(xiàn)它的類型相比,應(yīng)用范圍最廣,最穩(wěn)定。所以當調(diào)用者使用在廣大范圍內(nèi)長久有效的接口時,依賴的問題就失去意義了,因為依賴一個對象本身不是問題,問題是發(fā)生在對象失效或者需要替換時。
由上述討論可見,接口的第一個意義是它的高度抽象和由此帶來的廣泛適用性和穩(wěn)定性。
提倡針對接口編程的人往往會這樣推銷:接口是對一個對象行為和功能的描述,是對象暴露給外界的信息的總和,是對象之間交流的契約。如何實現(xiàn)接口則應(yīng)該是對象的隱私,是彼此間不知道也不應(yīng)該知道的內(nèi)部細節(jié)。一旦調(diào)用者獲取了被調(diào)用者接口之外的信息,例如通過被調(diào)用者的具體類型創(chuàng)建實例,或者調(diào)用了接口以外的方法,被調(diào)用者就失去了替換和修改的自由。這些堂皇的陳述看上去都很正確,但問題是怎樣理解“接口”。我們在學(xué)習(xí)面向?qū)ο缶幊虝r,都了解到相較于過程式編程,對象有三大好處或者說特點:封裝、繼承和多態(tài)。封裝的意思就是一個對象只暴露它想暴露的方法和屬性,外界無須知道的則隱藏起來。為此語言設(shè)計者發(fā)明了一堆存取限定符:public、private、protected、package,以精確地區(qū)分對象的信息對外界的可見性。那些標記為public的公開方法不就是一個對象的“接口”嗎?調(diào)用者只要通過這些方法來使用,被調(diào)用者的私有方法不還是可以自由修改和替換嗎?甚至更進一步說,即使在過程式編程中,一個函數(shù)暴露給外界的也僅僅是它的簽名(名稱、參數(shù)和返回值),實現(xiàn)的代碼同樣是隱藏的和可以修改的,函數(shù)的簽名不就是它的“接口”嗎?所以說,我們在Java這樣的靜態(tài)強類型語言中所說的接口,也就是語言中的Interface的要義不在于它包含的是一個對象公開的信息,而是這些信息是多個類型的對象共同具備的,也就是說,它描述的是多種對象具有的公開的共同點,因此另一個對象如果是通過這些共同點來使用這些對象中的一個,就可以隨時替換成其他任何一個。這是接口的第二個意義。
1.6.3 何時針對接口編程
理解了接口的意義,也就有了前述有關(guān)問題的判別準則。
任何注定不會有多個實現(xiàn)的接口都是不必要的。
這里的注定不是中國男子國家足球隊注定戰(zhàn)勝不了巴西隊的注定,幾十年后,滄海桑田,一切皆有可能。總之,這里的注定指的是開發(fā)者能預(yù)知的必定。在為一個類創(chuàng)建接口前,開發(fā)者可以略微思考一下在可預(yù)見的將來該類是否會一直是這個接口的唯一實現(xiàn)。很多時候,這樣的判斷并不是太難。比如寫一個在Word文檔中插入代碼的小工具,又或者為一家美容公司開發(fā)的項目中針對該公司特定規(guī)則的業(yè)務(wù)邏輯。用戶需求的特殊性決定了代碼的針對性(特殊性、選擇性),從而不會產(chǎn)生多個對象遵循同一接口而實現(xiàn)細節(jié)有差異的需要。需求的易變性(缺乏長期信息化的積累、最佳實踐和行業(yè)標準)導(dǎo)致代碼的頻繁改動,會使定義接口失去基礎(chǔ)。項目的規(guī)模、時限等因素引致的資源和進度緊張讓開發(fā)人員沒有時間去細致地設(shè)計接口,為不同的實現(xiàn)預(yù)留空間。
上面的判別準則是否定的陳述,我們還需要從肯定的角度來看什么情況下接口是必要的。最簡單的是完全相反的情況,凡是注定有多個實現(xiàn)的接口都是必要的。很多場合也是很容易做出這樣的判斷的。比如我們一直使用的編解碼器的例子,具體編解碼器類型不僅肯定有多個,而且會隨著新的媒體格式的出現(xiàn)和現(xiàn)有編解碼器的改進和嘗試而不斷增長。又比如Java和C#的Collections類庫中的各種接口,在設(shè)計的時候就預(yù)知會有多個實現(xiàn)類,并且將來還會不斷有類型因需要而實現(xiàn)。這些場合也有共同的規(guī)律可循。開發(fā)類庫時,常常會有一些對象的行為是基礎(chǔ)的、許多類型共有的,這樣的行為就適合被抽象為接口。設(shè)計框架、架構(gòu)和標準時,任務(wù)的目標往往就是抽象的接口,具體如何實現(xiàn)在設(shè)計時既有可能不清楚,更有可能是有意留給標準的參與者和遵循者未來去完成。軟件公司和開源組織推出可擴展的產(chǎn)品時,為了第三方能夠開發(fā)插件,必須提供接口。
然而許多程序員日常開發(fā)的項目不屬于這類情況——定制化的用戶界面和業(yè)務(wù)邏輯,系統(tǒng)完全在公司或組織內(nèi)部完成,沒有留給第三方開發(fā)的可能和空間。當處于上述正反兩個凡是準則的中間地帶時,就需要具體情況具體分析。特殊的、易變的、周邊的對象傾向于不需要接口;普遍用到因而可能產(chǎn)生變體的、穩(wěn)定的、核心的對象傾向于需要接口。項目和團隊的大小也很有關(guān)系。項目越大,需求越多,建模形成的系統(tǒng)越復(fù)雜,就越容易演化出多個類型遵循一組共同行為的結(jié)構(gòu)。另一方面,系統(tǒng)越龐大,對清晰的結(jié)構(gòu)、穩(wěn)定性和可擴展性的要求也就越高。項目規(guī)模大的一個衍生品是開發(fā)團隊人數(shù)多,隨之而來的是任務(wù)分解、分組負責(zé),每個小組需要能夠獨立開發(fā)負責(zé)的模塊,模塊又能方便地合作以完成最終的產(chǎn)品,這就要求在各個模塊間定義清晰的接口,實際上這種情況相當于前面所說的軟件公司開發(fā)可擴展的產(chǎn)品,第三方提供插件,本質(zhì)都是一個系統(tǒng)不是在一個開發(fā)人員群體內(nèi)部完成,該群體的人在設(shè)計時必須想到系統(tǒng)有部分功能是不在自己控制范圍之內(nèi)的,必須通過接口與他人合作。如此一來,就有一個項目開始時不大,后來因某種原因越變越大的情況,該如何處理?也就是說,系統(tǒng)成長后,有些對象需要接口了,而最初設(shè)計時根據(jù)前述的標準和考量是不需要的。我們應(yīng)該未雨綢繆,一開始就多定義接口嗎?那樣就回到前面否定的接口先行的老路上去了。還有一個不那樣做的可行性上的理由是,在系統(tǒng)誕生時就預(yù)先設(shè)計好將來會用到的接口幾乎是不可能的,隨著需求和目標的擴展和變化,依據(jù)用戶的反饋,包括引入接口這樣結(jié)構(gòu)上的變化是不可避免的,這也是重構(gòu)在項目開發(fā)中的重要性所在。而且幸運的是,借助現(xiàn)代開發(fā)環(huán)境的重構(gòu)工具,從一個類提取接口的工作可以輕松完成。
- FTTx PON技術(shù)與應(yīng)用
- 大話傳送網(wǎng)
- 現(xiàn)代雷達電子戰(zhàn)系統(tǒng)建模與仿真
- 微電子概論
- Altium Designer 21常見問題解答500例
- 液晶顯示器維修技能實訓(xùn)
- 全業(yè)務(wù)運營下網(wǎng)絡(luò)融合實現(xiàn)
- 路由器/交換機項目實訓(xùn)教程
- 光接入網(wǎng)實用技術(shù)
- 實例解讀電子元器件與電路設(shè)計
- 移動通信技術(shù)與應(yīng)用(第2版)
- Android移動應(yīng)用開發(fā)項目教程
- LED照明設(shè)計與應(yīng)用
- 5G改變世界
- Final Cut Pro X實戰(zhàn)從入門到精通