周立功教授數(shù)年之心血之作《程序設(shè)計(jì)與數(shù)據(jù)結(jié)構(gòu)》以及《面向AMetal框架與接口的編程(上)》,電子版已無償性分享到電子工程師與高校群體,。書本內(nèi)容公開后,在電子行業(yè)掀起一片學(xué)習(xí)熱潮。經(jīng)周立功教授授權(quán),本公眾號特對《程序設(shè)計(jì)與數(shù)據(jù)結(jié)構(gòu)》一書內(nèi)容進(jìn)行連載,愿共勉之。
第四章為面向?qū)ο缶幊?/span>,本文為 4.5框架與重用。
重用不僅限于軟件,比如,貝多芬在他的66號作品中,就重用了另一個(gè)偉大作曲家莫扎特的音樂。他從莫扎特的歌曲《魔笛》第22場中,借用了詠嘆調(diào)“一個(gè)女朋友”,然后在該詠嘆調(diào)中為鋼琴師配樂的大提琴家寫了一連串7個(gè)變奏。
代碼重用的問題與所有的設(shè)計(jì)方法一樣,代碼的可用性和可重用性取決于它是如何設(shè)計(jì)和實(shí)現(xiàn)的。雖然代碼重用并不是OO設(shè)計(jì)所專有的,但OO方法確實(shí)提供了一些機(jī)制,有利于可重用代碼的開發(fā)。
>>> 4.5.1 框架
框架被定義為“一組相互協(xié)作的類,形成某類軟件的一個(gè)可復(fù)用設(shè)計(jì)??蚣軐⒃O(shè)計(jì)劃分為一組抽象類,并定義它們各自的職責(zé)和相互之間的協(xié)作,以此指導(dǎo)體系結(jié)構(gòu)級的設(shè)計(jì),開發(fā)者通過繼承框架中的類和組合其實(shí)例定制該框架以生成特定的應(yīng)用。”
從某種意義上來說,框架是可以通過某種回調(diào)機(jī)制進(jìn)行擴(kuò)展的軟件系統(tǒng)或子系統(tǒng)的半成品。也就是說,首先,框架是半成品,這是它和其它所有軟件組件的本質(zhì)區(qū)別。而某種回調(diào)機(jī)制,通常面向過程編程使用函數(shù)指針作為參數(shù)實(shí)現(xiàn)回調(diào)機(jī)制,比如,冒泡排序和快速排序中的compare的形參就是一個(gè)函數(shù)指針,開發(fā)者只需知道自己實(shí)現(xiàn)特定的比較函數(shù)即可。而面向?qū)ο罂蚣艿慕M成部分包括具體類、抽象類和接口,使用抽象方法——多態(tài)支持回調(diào)機(jī)制實(shí)現(xiàn)逆向工程。
顯然,創(chuàng)建可重用代碼的一種方法就是創(chuàng)建框架,框架規(guī)定應(yīng)用的體系結(jié)構(gòu),它定義了整體結(jié)構(gòu),類和對象的分割,各部分的主要職責(zé),類和對象如何協(xié)作,以及控制流程。框架預(yù)定義了這些設(shè)計(jì)參數(shù),便于設(shè)計(jì)者聚焦于應(yīng)用本身的特定細(xì)節(jié)。但框架使開發(fā)程序變得更加容易,因此程序設(shè)計(jì)需要的許多能力都來源于大量可用的框架。
與代碼重用緊密相關(guān)的一個(gè)概念是標(biāo)準(zhǔn)化,有時(shí)也稱為即插即用,框架思想圍繞的就是這些即插即用和重用原則。在GUI應(yīng)用程序中,用戶界面視為視圖。而實(shí)際上在MVC框架中,視圖是一個(gè)接口,一個(gè)抽象的概念,因此視圖可能是一個(gè)用戶界面,也可能是一個(gè)終端,但只要實(shí)現(xiàn)了update接口的類,都可以將它們看作視圖,從而全面擴(kuò)展了MVC框架的應(yīng)用范圍。因?yàn)闊o論怎么改變,MVC框架的模型與視圖始終是不變的,可變的是具體模型和具體視圖。
以溫控器為例,通過傳感器的溫度檢測是具體模型,而監(jiān)聽傳感器的LED、數(shù)碼管和蜂鳴器是具體視圖。當(dāng)溫度達(dá)到或超過上限值時(shí),則數(shù)碼管更新顯示,LED持續(xù)閃爍,蜂鳴器持續(xù)報(bào)警。根據(jù)開閉原則,可以繼續(xù)重用MVC框架的抽象模型與視圖。如果后續(xù)只要開發(fā)與溫度檢測相關(guān)的系列產(chǎn)品,就可以重用該溫度檢測模型。
如果設(shè)計(jì)的系統(tǒng)必須使用不可移植的代碼,那么應(yīng)該將這些代碼抽象到類中,通過抽象將這些不可移植的代碼隔離到各自的類中。比如,針對基于M0+、M3、M4、ARM9、A7、A8內(nèi)核的ARM和DSP的不兼容性,周立功單片機(jī)公司開發(fā)的AWetal和AWorks就是一個(gè)將所有的接口、外圍器件和組件全部都實(shí)現(xiàn)歸一化,且與MCU和OS完全無關(guān)的框架,從而實(shí)現(xiàn)了“一次編程、終生使用、跨平臺”,詳見《面向接口的編程》系列圖書。
由此可見,由于軟件的整體框架結(jié)構(gòu)是一樣的,因此用戶不必學(xué)習(xí)新的框架;其次,開發(fā)人員只要遵循框架文檔提供的類或類庫的公共接口,以及應(yīng)用編程接口API等規(guī)則,就可以充分利用原有的代碼。
>>> 4.5.2 契約
抽象類與接口是實(shí)現(xiàn)代碼重用的強(qiáng)大機(jī)制,為一個(gè)重要的概念“契約”奠定了基礎(chǔ)。那什么是契約?契約是兩方或多方完成或不完成某個(gè)指定工作所達(dá)成的協(xié)議——這是一個(gè)由法律保證的協(xié)定,因此契約是要求開發(fā)人員遵守應(yīng)用編程接口規(guī)范所需的機(jī)制。
一般來說,API就是指一個(gè)框架,開發(fā)人員使用API時(shí),必須遵守框架所定義的規(guī)則。比如,方法名和參數(shù)個(gè)數(shù)等。如果沒有強(qiáng)制性的措施,一些比較差勁的程序員可能會私下編寫他自己的代碼,而不使用框架提供的規(guī)范。如果人們總是忽視或不考慮標(biāo)準(zhǔn)化,那么標(biāo)準(zhǔn)也就沒有什么意義了。
面向?qū)ο蟮脑O(shè)計(jì)一個(gè)重要的目標(biāo)就是將接口從實(shí)現(xiàn)中分離出來,一個(gè)類的接口提供了它的外部視圖,記錄了所有相關(guān)對象的共同屬性和行為。其強(qiáng)調(diào)的是抽象,隱藏了它的屬性和行為的秘密,不需要提供其內(nèi)部關(guān)于該操作的實(shí)現(xiàn)(結(jié)構(gòu))。
接口在很大程度上可以認(rèn)為是類的外部視圖的設(shè)計(jì)者和類的內(nèi)部實(shí)現(xiàn)的實(shí)現(xiàn)者之間的一種“契約”,同時(shí)也是需要(使用)該接口的類(如調(diào)用該接口所提供的操作)和提供該接口的類之間的一種約定。即將一個(gè)較大問題的不同功能通過子契約被分解為小問題,沒有別的情況比在設(shè)計(jì)類時(shí)更能體現(xiàn)這種思想。一個(gè)單獨(dú)的對象就是一個(gè)具體的實(shí)體,在系統(tǒng)中扮演某個(gè)角色。
將接口從實(shí)現(xiàn)中分離出來是通過抽象類來實(shí)現(xiàn)的,抽象類包含一個(gè)或多個(gè)沒有提供任何具體實(shí)現(xiàn)的方法。Validator之所以是一個(gè)抽象類,因?yàn)闊o法對它實(shí)例化。比如:
這與契約有什么關(guān)系?首先,希望所有與視圖對應(yīng)的顯示函數(shù)都使用相同的語法調(diào)用,比如,實(shí)現(xiàn)的每一種視圖都包含一個(gè)名為validate的方法。其次,每個(gè)類都要對自己的動作負(fù)責(zé),因此類不僅要提供相應(yīng)的方法,還必須提供它自己的實(shí)現(xiàn)代碼。比如:
由此可見,采用這種方式,就有了一個(gè)真正多態(tài)的Validator框架。系統(tǒng)中每個(gè)與視圖對應(yīng)的顯示函數(shù)都可以調(diào)用validaate方法,而調(diào)用每個(gè)與視圖對應(yīng)的顯示函數(shù)時(shí)都會得到不同的結(jié)果。實(shí)際上,向一個(gè)對象發(fā)送一個(gè)消息時(shí),會根據(jù)對象的不同而產(chǎn)生不同的響應(yīng),這正是多態(tài)的根本所在。
>>> 4.5.3 建立契約
定義契約的規(guī)則是通過抽象類提供一個(gè)未實(shí)現(xiàn)的方法,當(dāng)設(shè)計(jì)一個(gè)子類實(shí)現(xiàn)某個(gè)契約時(shí),它必須為父類中未實(shí)現(xiàn)的方法提供實(shí)現(xiàn),因?yàn)槠跫s帶來的好處可以標(biāo)準(zhǔn)化代碼。如果開發(fā)人員不遵循契約設(shè)計(jì)類,那么使用類的所有人都必須查看文檔。比如:
雖然這樣做也能夠?qū)崿F(xiàn)范圍值和奇偶校驗(yàn)功能,但不符合契約。因?yàn)槊嫦驅(qū)ο蟮闹饕獌?yōu)勢之一是可以重用類,重用的高層次的抽象接口比高度具體的接口更有用。
>>> 4.5.4 教條的危害
著名的語言專家王垠認(rèn)為,“很多編程的人喜歡鼓吹各種各樣的原則,并將那些所謂的原則奉為教條或者秘方。以為兢兢業(yè)業(yè)地遵循這些原則,空喊幾句口號,就可以寫出好的代碼。同時(shí)對違反這些原則的人嗤之以鼻——你不知道,不遵循或藐視這些原則,那么你就是菜鳥?!币虼瞬灰つ康孛孕鸥鞣N各樣的原則,比如,DRY原則(Don’t Repeat Yourself,不要重復(fù)你自己)在實(shí)際的工程中帶來了各種各樣的問題,卻經(jīng)常被忽視。DRY原則說,如果你發(fā)現(xiàn)重復(fù)的代碼,就提取它們?yōu)橐粋€(gè)父類。 然而“避免重復(fù)”并不等于“抽象”,有時(shí)候適當(dāng)?shù)闹貜?fù)代碼是有好處的。
代碼的“抽象”和它的“可讀性”,其實(shí)是相互矛盾的關(guān)系。適度的抽象和避免重復(fù)可以提高代碼的可讀性,如果你盡“一切可能”從代碼里提取共性,甚至將一些微不足道的“共性”也提出“共享”,反而破壞了程序的可閱讀性。如果盲目地將以下代碼:
修改為:
當(dāng)你看到TypeA和TypeB的定義時(shí),再也不能一目了然地看到int a。其實(shí)完全沒有必要提取這中無關(guān)緊要的共性,造出一個(gè)新的父類,因?yàn)榭梢娦允浅绦騿T產(chǎn)生直覺的關(guān)鍵。奉行DRY原則的人存在的問題,在于是他們隨時(shí)都在試圖發(fā)現(xiàn)“將來可能重用”的代碼,而不是等到真的出現(xiàn)重復(fù)的時(shí)候再去做抽象。
抽象思想的關(guān)鍵在于“發(fā)現(xiàn)兩個(gè)東西是一樣的”,然而很多時(shí)候,開始時(shí)覺得兩個(gè)東西是一回事,最后發(fā)現(xiàn)它們其實(shí)只是膚淺的相似,而本質(zhì)完全不同。同一個(gè)int a,其實(shí)可以表示很多種風(fēng)馬牛不及的性質(zhì)。你看到都是int a就提出來為父類,反而讓程序的概念變得混亂。有些東西開始時(shí)貌似同類,當(dāng)添加了新的邏輯之后,發(fā)現(xiàn)它們的用途開始特殊化了。因此過早地提取共性,反而捆住了手腳,為了所謂的“一致性”,而重復(fù)一些沒用的東西。這樣的一致性,其實(shí)還不如針對每種情況分別做特殊處理。
防止過早抽象的方法其實(shí)很簡單,它的名字叫做“等待”。其實(shí)就算你不重用代碼,也不會影響程序的準(zhǔn)確性和可讀性,時(shí)間能夠告訴你一切。如果你發(fā)現(xiàn)自己仿佛正在重復(fù)以前寫過代碼,請先不要停下來,堅(jiān)持將這段重復(fù)的代碼寫完。如果你不將它寫出來,你將無法準(zhǔn)確地發(fā)現(xiàn)重復(fù)的代碼,因?yàn)樗鼈兒苡锌赡艿阶詈笃鋵?shí)是不一樣的。
我們應(yīng)該避免沒有實(shí)際效果的抽象,如果代碼才重復(fù)了兩次,就開始提取共性,也許到最后會發(fā)現(xiàn),這個(gè)模板總共也就只用了兩次。只重復(fù)了兩次的代碼,大部分時(shí)候是不值得為它提取模板的。因?yàn)槟0灞旧硪彩谴a,而且抽象思考本身是需要一定代價(jià)的。所以最后總的開銷,也許還不如就讓那兩段重復(fù)的代碼待在里面。
而優(yōu)秀的程序員等到事實(shí)證明重用一定會帶來好處時(shí),才會開始提取共性進(jìn)行抽象。實(shí)踐經(jīng)驗(yàn)證明,每一次積極地尋找抽象,最后的結(jié)果都是制造一些不必要的框架,搞得自己的代碼自己都看不懂。如果過度地強(qiáng)調(diào)DRY,強(qiáng)調(diào)代碼的“重用”,隨時(shí)隨地想著抽象,結(jié)果就會被這些抽象攪混了頭腦,bug百出寸步難行。如果你不能寫出“可用”的代碼,又何談“可重用”的代碼呢?
其實(shí)人們寫程序本來自然而然就會在合適的時(shí)候進(jìn)行抽象避免重復(fù),因此千萬不要迷信某個(gè)大師或?qū)<移鹆艘粋€(gè)DRY這樣的名字,就將我們繞進(jìn)去了,反而使我們喪失了透過現(xiàn)象看本質(zhì)的思維能力。
回頭來看,里氏替換原則也沒有什么特別之處,無論是否有人提出這樣的原則,子類對象與父類對象的地址值相等且類型相同,這是在語言層面天生就支持的行為。比如,雖然&rangeValidator與&rangeValidator.isa的類型不同,但它們的值相等。由于&rangeValidator.isa與pThis不僅類型相同它們的值相等,因此子類對象替換父類對象也就成為了事實(shí)。
當(dāng)你看透了問題的本質(zhì)之后,也就具備了洞穿一切的能力。顯然里氏替換原則只是套了一個(gè)馬甲而已,因此人們常說,“盡信書不如無書”,由此可見不無道理。
-
程序設(shè)計(jì)
+關(guān)注
關(guān)注
3文章
261瀏覽量
30352
原文標(biāo)題:周立功:盡信書不如無書
文章出處:【微信號:ZLG_zhiyuan,微信公眾號:ZLG致遠(yuǎn)電子】歡迎添加關(guān)注!文章轉(zhuǎn)載請注明出處。
發(fā)布評論請先 登錄
相關(guān)推薦
評論