




版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請(qǐng)進(jìn)行舉報(bào)或認(rèn)領(lǐng)
文檔簡介
編程語言實(shí)現(xiàn)模式(中文版)目錄\h第1章引言\h1.1章節(jié)概覽\h1.2那么,現(xiàn)在……\h第2章模式\h第3章一種編程理論\h3.1價(jià)值觀\h3.1.1溝通\h3.1.2簡單\h3.1.3靈活\h3.2原則\h3.2.1局部化影響\h3.2.2最小化重復(fù)\h3.2.3將邏輯與數(shù)據(jù)捆綁\h3.2.4對(duì)稱性\h3.2.5聲明式表達(dá)\h3.2.6變化率\h3.3小結(jié)\h第4章動(dòng)機(jī)\h第5章類\h5.1類\h5.2簡單的超類名\h5.3限定性的子類名\h5.4抽象接口\h5.5interface\h5.6抽象類\h5.7有版本的interface\h5.8值對(duì)象\h5.9特化\h5.10子類\h5.11實(shí)現(xiàn)器\h5.12內(nèi)部類\h5.13實(shí)例特有的行為\h5.14條件語句\h5.15委派\h5.16可插拔的選擇器\h5.17匿名內(nèi)部類\h5.18庫類\h5.19小結(jié)\h第6章狀態(tài)\h6.1狀態(tài)\h6.2訪問\h6.3直接訪問\h6.4間接訪問\h6.5通用狀態(tài)\h6.6可變狀態(tài)\h6.7外生狀態(tài)\h6.8變量\h6.9局部變量\h6.10字段\h6.11參數(shù)\h6.12收集參數(shù)\h6.13可選參數(shù)\h6.14變長參數(shù)\h6.15參數(shù)對(duì)象\h6.16常量\h6.17按角色命名\h6.18聲明時(shí)的類型\h6.19初始化\h6.20及早初始化\h6.21延遲初始化\h6.22小結(jié)\h第7章行為\h7.1控制流\h7.2主體流\h7.3消息\h7.4選擇性消息\h7.5雙重分發(fā)\h7.6分解性(序列性)消息\h7.7反置性消息\h7.8邀請(qǐng)性消息\h7.9解釋性消息\h7.10異常流\h7.11衛(wèi)述句\h7.12異常\h7.13已檢查異常\h7.14異常傳播\h7.15小結(jié)\h第8章方法\h8.1組合方法\h8.2揭示意圖的名稱\h8.3方法可見性\h8.4方法對(duì)象\h8.5覆蓋方法\h8.6重載方法\h8.7方法返回類型\h8.8方法注釋\h8.9助手方法\h8.10調(diào)試輸出方法\h8.11轉(zhuǎn)換\h8.12轉(zhuǎn)換方法\h8.13轉(zhuǎn)換構(gòu)造器\h8.14創(chuàng)建\h8.15完整的構(gòu)造器\h8.16工廠方法\h8.17內(nèi)部工廠\h8.18容器訪問器方法\h8.19布爾值設(shè)置方法\h8.20查詢方法\h8.21相等性判斷方法\h8.22取值方法\h8.23設(shè)置方法\h8.24安全復(fù)制\h8.25小結(jié)\h第9章容器\h9.1隱喻\h9.2要點(diǎn)\h9.3接口\h9.3.1Array\h9.3.2Iterable\h9.3.3Collection\h9.3.4List\h9.3.5Set\h9.3.6SortedSet\h9.3.7Map\h9.4實(shí)現(xiàn)\h9.4.1Collection\h9.4.2List\h9.4.3Set\h9.4.4Map\h9.5Collections\h9.5.1查詢\h9.5.2排序\h9.5.3不可修改的容器\h9.5.4單元素容器\h9.5.5空容器\h9.6繼承容器\h9.7小結(jié)\h第10章改進(jìn)框架\h10.1修改框架而不修改應(yīng)用\h10.2不兼容的更新\h10.3鼓勵(lì)可兼容的變化\h10.3.1程序庫類\h10.3.2對(duì)象\h10.4小結(jié)\h附錄A性能度量\hA.1示例\hA.2API\hA.3實(shí)現(xiàn)\hA.4MethodTimer\hA.5沖抵額外開銷\hA.6測試\hA.6.1容器的比較\hA.6.2ArrayList和LinkedList的比較\hA.6.3Set之間的比較\hA.6.4Map之間的比較\hA.7小結(jié)\h第1章引言現(xiàn)在開始吧。你選中了我的書(現(xiàn)在它就是你的了),你也編寫過代碼,很可能你已經(jīng)從自己的經(jīng)驗(yàn)中建立了一套屬于自己的風(fēng)格。這本書的目標(biāo)是要幫助你通過代碼表達(dá)自己的意圖。首先,我們對(duì)編程和模式做一個(gè)概述(第2章~第4章)。接下來(第5章~第8章)用一系列短文和模式,講述了“如何用Java編寫出可讀的代碼”。如果你正在編寫框架(而不是應(yīng)用程序),最后一章會(huì)告訴你如何調(diào)整前面給出的建議??偠灾緯P(guān)注的焦點(diǎn)是用編程技巧來增進(jìn)溝通。用代碼來溝通有幾個(gè)步驟。首先,必須在編程時(shí)保持清醒。第一次開始記錄實(shí)現(xiàn)模式時(shí),我編程已經(jīng)有一些年頭了。我驚訝地發(fā)現(xiàn),盡管能夠快捷流暢地作出各種編程中的決定,但我沒法解釋自己為什么如此確定諸如“這個(gè)方法為什么應(yīng)該被這樣調(diào)用”或者“那塊代碼為什么屬于那個(gè)對(duì)象”之類的事情。邁向溝通的第一步就是讓自己慢下來,弄明白自己究竟想了些什么,不再假裝自己是在憑本能編程。第二步是要承認(rèn)他人的重要性。我很早就發(fā)現(xiàn)編程是一件令人滿足的事,但我是個(gè)以自我為中心的人,所以必須學(xué)會(huì)相信其他人也跟我一樣重要,然后才能寫出能與他人溝通的代碼。編程很少會(huì)是一個(gè)人與一臺(tái)機(jī)器之間孤獨(dú)的交流,我們應(yīng)該學(xué)會(huì)關(guān)心其他人,而這需要練習(xí)。所以我邁出了第三步。一旦把自己的想法暴露在光天化日下,并且承認(rèn)別人也有權(quán)和我一樣地存在,我就必須實(shí)實(shí)在在地展示自己的新觀點(diǎn)了。我使用本書中介紹的實(shí)現(xiàn)模式,目的是更有意識(shí)地編程,為他人編程,而不僅僅是為自己編程。你當(dāng)然可以僅為其中的技術(shù)內(nèi)容——有用的技巧和解釋——而閱讀本書,但我認(rèn)為應(yīng)該預(yù)先提醒你,除了這些技術(shù)內(nèi)容,本書還包含了很多東西,至少對(duì)我而言是這樣。這些技術(shù)內(nèi)容都可以在介紹模式的章節(jié)里找到。學(xué)習(xí)這部分內(nèi)容有一個(gè)高效的策略:需要用的時(shí)候再去讀。如果用這種“just-in-time”的方式來讀,那么可以直接跳到第5章,把后續(xù)的章節(jié)快速掃一遍,然后在編程時(shí)把本書放在手邊。用過書中的很多模式之后,你可以重新回到前面介紹性的內(nèi)容中來,讀一下那些技巧背后的道理。如果有興趣透徹理解手上的這本書,也可以細(xì)細(xì)地從頭讀到尾。和我寫過的大部分書不同,這本書的每一章都相當(dāng)長,因此在閱讀時(shí)要保持專注才行。書中的大部分內(nèi)容都以模式的形式加以組織。編程中需要做的抉擇大多曾經(jīng)出現(xiàn)過。一個(gè)程序員的職業(yè)生涯中可能要給上百萬個(gè)變量命名,不可能每次都用全新的方式來命名。命名的普遍約束總是一致的:需要把變量的用途、類型和生命周期告訴給閱讀者,需要挑選一個(gè)容易讀懂的名字,需要挑選一個(gè)容易寫、符合標(biāo)準(zhǔn)格式的名字。把這些普遍約束加諸一個(gè)具體的變量之上,然后就得到了一個(gè)合用的名字?!敖o變量命名”就是一個(gè)模式:盡管每次都可能創(chuàng)造出不同的名字,但決策的方法和約束條件總是重復(fù)出現(xiàn)的。我覺得,模式需要以多種形式來呈現(xiàn),有時(shí)一篇充滿爭議的文章能最好地闡釋一個(gè)模式,有時(shí)候是一幅圖,有時(shí)候是一個(gè)故事,有時(shí)候是一段示例。所以我并沒有嘗試把所有模式都塞進(jìn)同一種死板的格式里,而是以我認(rèn)為最恰當(dāng)?shù)姆绞絹砻枋鏊鼈?。書中總共包含?7個(gè)明確命名的模式,它們分別涵蓋了“編寫可讀的代碼”這件事的某一方面。此外我還在書中提到了很多更小的模式或是模式的變體。我寫這本書的目的是給程序員們一點(diǎn)建議,告訴他們?nèi)绾卧诿刻熳钇椒驳木幊倘蝿?wù)中幫助將來的閱讀者理解代碼的意圖。本書的深度應(yīng)該介于DesignPatterns(中譯版《設(shè)計(jì)模式:可復(fù)用面向?qū)ο筌浖幕A(chǔ)》)和Java語言手冊(cè)之間。DesignPatterns討論的是開發(fā)過程中每天要做幾次的那種決策,通常是關(guān)于如何協(xié)調(diào)對(duì)象之間交互的決策,實(shí)現(xiàn)模式的粒度更小。編程時(shí)每過幾秒鐘就可能用上一個(gè)模式。語言手冊(cè)很好地介紹了“能用Java做什么”,但對(duì)于“為什么使用某種結(jié)構(gòu)”或者“別人讀到這段代碼時(shí)會(huì)怎么想”這樣的問題談?wù)撋跎?,而這正是實(shí)現(xiàn)模式的重點(diǎn)所在。在寫這本書時(shí),我的一個(gè)原則就是只寫我熟悉的主題。比如說并發(fā)(concurrency)問題就沒有涵蓋在這些實(shí)現(xiàn)模式中,并非因?yàn)椴l(fā)不重要,只是因?yàn)槲覍?duì)這個(gè)主題沒有太多可說的。我對(duì)待并發(fā)問題的策略一向很簡單:盡可能地把涉及并發(fā)的部分從我的程序中隔離出去。雖然我一直用這個(gè)辦法還干得不錯(cuò),但這確實(shí)沒有多少可解釋的。更多關(guān)于并發(fā)處理的實(shí)踐指導(dǎo),我推薦諸如JavaConcurrencyinPractice(中譯版《Java并發(fā)編程實(shí)踐》)之類的書籍。本書完全沒有涉及的另一個(gè)主題是軟件過程。我給出的建議只是告訴閱讀者如何用代碼來交流,不管這代碼是在一個(gè)漫長流程的最后階段編寫出來的,還是在編寫出一個(gè)無法通過的測試之后立即編寫出來的,我希望這些建議都同樣適用??偠灾还芄谝栽鯓拥拿?,只要能降低軟件開發(fā)的成本就是好事。此外本書也盡量避免使用Java的最新特性。我在選擇技術(shù)時(shí)總是傾向于保守,因?yàn)闊o所不用其極地嘗試新技術(shù)已經(jīng)傷害過我太多次了(作為學(xué)習(xí)新技術(shù)的策略,這很好;但對(duì)于大部分開發(fā)工作而言,風(fēng)險(xiǎn)太大)。所以,你會(huì)在本書中看到一個(gè)非常樸實(shí)的Java子集。如果希望使用Java的最新特性,可以從別的地方去學(xué)習(xí)它們。\h1.1章節(jié)概覽本書總共分成了7大塊,如圖1.1所示,分別是:總體介紹(Introduction)——這幾個(gè)簡短的章節(jié)描述了“用代碼溝通”的重要性與價(jià)值所在,以及實(shí)現(xiàn)模式背后的思想;類(Class)——這部分的模式講述了為什么要?jiǎng)?chuàng)建類,如何創(chuàng)建類,如何用類來書寫邏輯等問題;狀態(tài)(State)——關(guān)于狀態(tài)存取的模式;行為(Behavior)——這部分的模式告訴閱讀者如何用代碼來表現(xiàn)邏輯,特別是如何用多種不同的方式來做這件事;方法(Methods)——關(guān)于如何編寫方法的模式,它們將告訴你,根據(jù)你對(duì)方法的分解和命名,閱讀者會(huì)作出怎樣的判斷;容器(Collections)——關(guān)于選擇和使用容器的模式;改進(jìn)框架(Frameworks)——上述模式的變體,適用于框架開發(fā)(而非應(yīng)用程序開發(fā))。圖1.1全書概覽\h1.2那么,現(xiàn)在……該言歸正傳了。如果你打算按部就班地讀下去,請(qǐng)翻到下一頁(我猜這用不著特別提醒)。如果想快速瀏覽所有的模式,請(qǐng)從第5章開始。\h第2章模式編程中的很多決策是無法復(fù)制的。開發(fā)網(wǎng)站的方式與開發(fā)心臟起搏器控制軟件的方式肯定迥然不同。但決策的內(nèi)容越接近純技術(shù)化,其中的相似性就越多,我不是剛編寫過一樣的代碼嗎?程序員為不斷重復(fù)的瑣事耗費(fèi)的時(shí)間越少,他們就有越多的時(shí)間來解決好真正獨(dú)一無二的問題,從而更高效地編程。絕大多數(shù)程序都遵循一組簡單的法則。更多的時(shí)候,程序是在被閱讀,而不是被編寫。沒有“完工”一說。修改程序的投入會(huì)遠(yuǎn)大于最初編寫程序的投入。程序都由一組基本的語句和控制流概念組合而成。程序的閱讀者需要理解程序——既從細(xì)節(jié)上,也從概念上。有時(shí)他們從細(xì)節(jié)開始,逐漸理解概念;有時(shí)他們從概念開始,逐漸理解細(xì)節(jié)。模式就是基于這樣的共性之上的。比如說,每個(gè)程序員都必須決定如何進(jìn)行迭代遍歷。在思考如何寫出循環(huán)的時(shí)候,大部分領(lǐng)域問題都被暫時(shí)拋在腦后了,留下的就是純技術(shù)問題:這個(gè)循環(huán)應(yīng)該容易讀懂,容易編寫,容易驗(yàn)證,容易修改,而且高效。讓你操心的這一系列事情,就是模式的源起。上面列出的這些約束,或者叫壓力(force),會(huì)影響程序中每個(gè)循環(huán)編寫的方式??梢灶A(yù)見到,這樣的壓力會(huì)不斷重現(xiàn),這也正是模式之所以成為模式的原因:它其實(shí)是關(guān)于壓力的模式。有好幾種合理的方式可以寫出一個(gè)循環(huán),它們分別暗含著對(duì)這些約束不同的優(yōu)先級(jí)排序:如果性能更重要,你可能用這種方式來寫循環(huán);如果容易修改更重要,你就可能用另一種方式來寫循環(huán)。每個(gè)模式都代表著一種對(duì)壓力進(jìn)行相對(duì)優(yōu)先級(jí)排序的觀點(diǎn)。大部分模式都由一篇短文來描述,其中列舉出解決某一問題的各種方案,以及推薦方案的優(yōu)點(diǎn)所在。這些模式不僅給出一個(gè)建議,而且還講出背后的原因,這樣閱讀者就可以自己判斷應(yīng)該如何解決這類重復(fù)出現(xiàn)的問題。正如前面暗示的,每個(gè)模式也都帶著一個(gè)解決方案的種子。關(guān)于“循環(huán)遍歷一個(gè)容器”的模式可能會(huì)建議說“使用Java5的for循環(huán)來描述遍歷操作”。模式在抽象的原則和具體的實(shí)踐之間架起了一座橋梁。模式可以幫助你編寫代碼。模式彼此協(xié)作。建議你用for循環(huán)的模式,又引出了“如何給循環(huán)變量命名”的問題。我們不嘗試把所有事情都塞進(jìn)一個(gè)模式里,還有另一個(gè)模式專門講“如何給變量命名”的話題。模式在本書中有多種不同的展現(xiàn)形式:有時(shí)它們有清晰的名稱,還有專門的章節(jié)來討論壓力和解決方案。但也有時(shí),一些比較小的模式就直接放在更大的模式內(nèi)部來介紹,一兩句話或許就能夠把一個(gè)小模式討論清楚了。使用模式有時(shí)會(huì)讓你感到束手束腳,但確實(shí)可以幫你節(jié)省時(shí)間和精力。打個(gè)比方,就好像鋪床這件小事,如果每次都必須思考每個(gè)步驟怎么做、找出正確的順序,那就會(huì)比習(xí)慣成自然的做法耗費(fèi)更多的精力。正是因?yàn)橛幸唤M鋪床的模式,這件事情才得以大大簡化。如果床恰好頂在墻邊,或者床單太小,你會(huì)根據(jù)情況調(diào)整策略,但整體來說還是遵循固定模式來鋪床,這樣你的腦子就可以用來思考更有意思、更有必要的東西。編程也是一樣,當(dāng)模式成為習(xí)慣之后,我很開心地發(fā)現(xiàn)自己不必再為“如何寫一個(gè)循環(huán)”而展開討論了。如果整個(gè)團(tuán)隊(duì)都對(duì)一個(gè)模式不滿,那么他們可以討論引入新的模式。沒有任何一組模式能夠適用于所有情況。本書中列出的模式是我在應(yīng)用程序開發(fā)中親自用過的,或者看到別人用過并且效果不錯(cuò)的(后文也淺談了一下框架開發(fā)中的模式)。盲目效仿別人的風(fēng)格,永遠(yuǎn)都不如思考和實(shí)踐自己的風(fēng)格并在團(tuán)隊(duì)中討論交流來得有效。模式最大的作用就是幫助人們做決定。有些實(shí)現(xiàn)模式最終會(huì)融入編程語言,就好像setjmp()/longjmp()結(jié)構(gòu)變成了如今的異常處理。不過大部分時(shí)候,模式需要加以調(diào)整才能投入使用。從這一章開始,我們?cè)噲D尋找一種更節(jié)約、更快速、更省力的方式來解決常見的編程問題。使用模式可以幫助程序員用更合理的方式來解決常見問題,從而把更多的時(shí)間、精力和創(chuàng)造力留下來解決真正獨(dú)一無二的問題。每個(gè)模式都涉及一個(gè)常見的編程問題,隨后我們會(huì)討論其中起影響作用的各種因素,并提出具體的建議:如何快速實(shí)現(xiàn)一個(gè)令人滿意的解決方案。其結(jié)果是,這些模式將幫助讀者更好、更快、更省力地完成編程工作中乏味的部分,從而留下更多的時(shí)間和精力來解決程序中獨(dú)一無二的問題。本書中的實(shí)現(xiàn)模式共同構(gòu)筑了一種編程風(fēng)格,下一章“一種編程理論”將會(huì)介紹這種編程風(fēng)格背后的價(jià)值觀和原則。\h第3章一種編程理論就算是再巨細(xì)靡遺的模式列表,也不可能涵蓋編程中所遇到的每一種情況。你免不了(甚至常常)會(huì)遭遇到這種情景:上窮碧落,也找不到對(duì)應(yīng)的現(xiàn)成解決方案。于是便需要有針對(duì)特定問題的通用解決方案。這也正是學(xué)習(xí)編程理論的原因之一。原因之二則是那種知曉如何去做、為何去做之后所帶來的胸有成竹。當(dāng)然,如果把編程的理論和實(shí)踐結(jié)合起來討論,內(nèi)容就會(huì)更加精彩了。每個(gè)模式都承載著一點(diǎn)點(diǎn)理論。但實(shí)際編程中存在一些更加深廣的影響力,遠(yuǎn)不是孤立的模式所能概括的。本章將會(huì)講述這些貫穿于編程中的橫切概念,它們分為兩類:價(jià)值觀與原則。價(jià)值觀是編程過程的統(tǒng)一支配性主題。珍視與其他人溝通的重要性,把代碼中多余的復(fù)雜性去掉,并保持開放的心態(tài),這才是我工作狀態(tài)最佳的表現(xiàn)。這些價(jià)值觀——溝通、簡單和靈活——影響了我在編程時(shí)所做的每個(gè)決策。此處描述的原則不像上面的價(jià)值觀那樣意義深遠(yuǎn),不過每一項(xiàng)原則都在許多模式中得以體現(xiàn)。價(jià)值觀有普遍的意義,但往往難以直接應(yīng)用;模式雖可以直接應(yīng)用,卻是針對(duì)于特定情景;原則在價(jià)值觀和模式之間搭建了橋梁。我早已發(fā)現(xiàn),在那種沒有模式可以應(yīng)用,或是兩個(gè)相互排斥的模式可以同等應(yīng)用的場合,如果把編程原則弄清楚,對(duì)解決疑難會(huì)是一件好事。在面對(duì)不確定性的時(shí)候,對(duì)原則的理解讓我可以“無中生有”創(chuàng)造出一些東西,同時(shí)能和其他的實(shí)踐保持一致,而且結(jié)果一般都不錯(cuò)。價(jià)值觀、原則和模式,這3種元素互為補(bǔ)充,組成了一種穩(wěn)定的開發(fā)方式。模式描述了要做什么,價(jià)值觀提供了動(dòng)機(jī),原則把動(dòng)機(jī)轉(zhuǎn)化成了實(shí)際行動(dòng)。這里的價(jià)值觀、原則和模式,是通過我的親身實(shí)踐、反思以及與其他人的討論總結(jié)出來的。我們都曾經(jīng)從前人那里吸收經(jīng)驗(yàn),最終會(huì)形成一種開發(fā)方式,但不是唯一的開發(fā)方式。不同的價(jià)值觀和不同的原則會(huì)產(chǎn)生不同的方式。把編程方式用價(jià)值觀、原則和模式的形式展現(xiàn)出來,其優(yōu)點(diǎn)之一就是可以更加有效地展現(xiàn)編程方法的差異。如果你喜歡用某種方式來做事,而我喜歡另一種,那么就可以識(shí)別出我們?cè)谀姆N層次上存在分歧,從而避免浪費(fèi)時(shí)間。如果我們各自認(rèn)可不同的原則,那么爭論哪里該用大括號(hào)根本無助于解決問題。\h3.1價(jià)值觀有3個(gè)價(jià)值觀與卓越的編程血脈相連,它們分別是:溝通、簡單和靈活。雖然它們有時(shí)候會(huì)有所沖突,但更多的時(shí)候則是相得益彰。最優(yōu)秀的程序會(huì)為未來的擴(kuò)展留下充分的選擇余地,不包含不相關(guān)的元素,容易閱讀,容易理解。\h3.1.1溝通如果閱讀者可以理解某段代碼,并且進(jìn)一步修改或使用它,那么這段代碼的溝通效果就很好。在編程時(shí),我們很容易從計(jì)算機(jī)的角度進(jìn)行思考。但只有一面編程一面考慮其他人的感受,我才能編寫出好的代碼。在這種前提下編寫出的代碼更加干凈易讀,更有效率,更清晰地展現(xiàn)出我的想法,給了我全新的視角,減輕了我的壓力。我的一些社會(huì)性需要得到了自我滿足。最開始編程吸引我的部分原因在于我可以通過編程與外界交流,然而,我不想與那些難纏又無法理喻的煩人家伙打交道。過了20年,把別人當(dāng)作空氣一樣的編程方式才在我眼中褪盡了顏色。耗盡心神去精心搭建一座糖果城堡,于我而言已毫無意義。Knuth所提出的文學(xué)編程理論促使我把注意力放到溝通上來:程序應(yīng)該讀起來像一本書一樣。它需要有情節(jié)和韻律,句子間應(yīng)該有優(yōu)雅的小小跌宕起伏。我和WardCunningham第一次接觸到文學(xué)性程序這個(gè)概念以后,我們決定來試一試。我們找出Smalltalk中最干凈的代碼之一——ScrollController,坐到一起,然后試著把它寫成一個(gè)故事。幾個(gè)小時(shí)以后,我們以自己的方式完全重寫了這段代碼,把它變成了一篇合情合理的文章。每次遇到難以解釋清楚的邏輯,重新把它寫一遍都要比解釋這段代碼為何難以理解容易得多。溝通的需要改變了我們對(duì)于編碼的看法。在編程時(shí)注重溝通還有一個(gè)很明顯的經(jīng)濟(jì)學(xué)基礎(chǔ)。軟件的絕大部分成本都是在第一次部署以后才產(chǎn)生的。從我自己修改代碼的經(jīng)驗(yàn)出發(fā),我花在閱讀既有代碼上的時(shí)間要比編寫全新的代碼長得多。如果我想減少代碼所帶來的開銷,我就應(yīng)該讓它容易讀懂。注重溝通還可以幫助我們改進(jìn)思想,讓它更加現(xiàn)實(shí)。一方面是由于投入更多的思考,考慮“如果別人看到這段代碼會(huì)怎么想”所需要調(diào)動(dòng)的腦細(xì)胞,和只關(guān)注自己是不一樣的。這時(shí)我會(huì)退后一步,從全新的視角來審視面對(duì)的問題和解決方案。另一方面則是由于壓力的減輕,因?yàn)槲抑雷约核龅氖虑槭窃趧?wù)正業(yè),我做的是對(duì)的。最后,作為社會(huì)性的產(chǎn)物,明確地考慮社會(huì)因素要比在假設(shè)它們不存在的情況下工作更為現(xiàn)實(shí)。\h3.1.2簡單在VisualDisplayofQuantitativeInformation一書中,EdwardTufte做過一個(gè)實(shí)驗(yàn),他拿過一張圖,把上面沒有增加任何信息的標(biāo)記全都擦掉,最終得到了一張很新穎的圖,比原先那張更容易理解。去掉多余的復(fù)雜性可以讓那些閱讀、使用和修改代碼的人更容易理解。有些復(fù)雜性是內(nèi)在的,它們準(zhǔn)確地反映出所要解決的問題的復(fù)雜性。但有些復(fù)雜性的產(chǎn)生完全是因?yàn)槲覀兠χ尦绦蜻\(yùn)行起來,在擺弄過程中留下來的“指甲印”沒擦干凈。這種多余的復(fù)雜性降低了軟件的價(jià)值,因?yàn)橐环矫孳浖_運(yùn)行的可能性降低了,另一方面將來也很難進(jìn)行正確的改動(dòng)?;仡欁约鹤鲞^的事情,把麥子和糠分開,是編程中不可或缺的一部分。簡單存在于旁觀者的眼中。一個(gè)可以將專業(yè)工具使用得得心應(yīng)手的高級(jí)程序員,他所認(rèn)為的簡單事情對(duì)一個(gè)初學(xué)者來說可能會(huì)比登天還難。只有把讀者放在心里,你才可以寫出動(dòng)人的散文。同樣,只有把讀者放在心里,你才可以編寫出優(yōu)美的程序。給閱讀者一點(diǎn)挑戰(zhàn)沒有關(guān)系,但過多的復(fù)雜性會(huì)讓你失去他們。在復(fù)雜與簡單的波動(dòng)中,計(jì)算機(jī)技術(shù)不斷向前推進(jìn)。直到微型計(jì)算機(jī)出現(xiàn)之前,大型機(jī)架構(gòu)的發(fā)展傾向仍然是越來越復(fù)雜。微型計(jì)算機(jī)并沒有解決大型機(jī)的所有問題,只不過在很多應(yīng)用中,那些問題已經(jīng)變得不再重要。編程語言也在復(fù)雜和簡單的起伏中前行。C++在C的基礎(chǔ)上產(chǎn)生,而后在C++的基礎(chǔ)上又出現(xiàn)了Java,現(xiàn)在Java本身也變得越來越復(fù)雜了。追求簡單推動(dòng)了進(jìn)化。JUnit比它所大規(guī)模取代的上一代測試工具簡單得多。JUnit催生了各種模仿者、擴(kuò)展軟件和新的編程/測試技術(shù)。它最近一個(gè)版本JUnit4已經(jīng)失去了那種“一目了然”的效果,雖然每一個(gè)導(dǎo)致其復(fù)雜化的決定都有我參與其中,但亦未能阻止這種趨勢(shì)??傆幸惶?,會(huì)有人發(fā)明一種比JUnit簡單許多的方式,以方便編程人員編寫測試。這種新的想法又會(huì)推動(dòng)另一輪進(jìn)化。在各個(gè)層次上都應(yīng)當(dāng)要求簡單。對(duì)代碼進(jìn)行調(diào)整,刪除所有不提供信息的代碼。設(shè)計(jì)中不出現(xiàn)無關(guān)元素。對(duì)需求提出質(zhì)疑,找出最本質(zhì)的概念。去掉多余的復(fù)雜性后,就好像有一束光照亮了余下的代碼,你就有機(jī)會(huì)用全新的視角來處理它們。溝通和簡單通常都是不可分割的。多余的復(fù)雜性越少,系統(tǒng)就越容易理解;在溝通方面投入越多,就越容易發(fā)現(xiàn)應(yīng)該被拋棄的復(fù)雜性。不過有時(shí)候我也會(huì)發(fā)現(xiàn)某種簡化會(huì)使程序難以理解,這種情況下我會(huì)優(yōu)先考慮溝通。這樣的情形很少,但常常都表示這里應(yīng)該有一些我尚未察覺的更大規(guī)模的簡化。\h3.1.3靈活在三種價(jià)值觀中,靈活是衡量那些低效編碼與設(shè)計(jì)實(shí)踐的一把標(biāo)尺。以獲取一個(gè)常量為例,我曾經(jīng)見到有人會(huì)用環(huán)境變量保存一個(gè)目錄名,而那個(gè)目錄下放著一個(gè)文件,文件中寫著那個(gè)常量的值。為什么弄這么復(fù)雜?為了靈活。程序是應(yīng)該靈活,但只有在發(fā)生變化的時(shí)候才需如此。如果這個(gè)常量永遠(yuǎn)不會(huì)變化,那么付出的代價(jià)就都白費(fèi)了。因?yàn)槌绦虻慕^大部分開銷都是在它第一次部署以后才產(chǎn)生,所以程序必須要容易改動(dòng)。想象中明天或許會(huì)用得上的靈活性,可能與真正修改代碼時(shí)所需要的靈活性不是一回事。這就是簡單性和大規(guī)模測試所帶來的靈活性比專門設(shè)計(jì)出來的靈活性更為有效的原因。要選擇那些提倡靈活性并能夠帶來及時(shí)收益的模式。對(duì)于會(huì)立刻增加成本但收效卻緩慢的模式,最好讓自己多一點(diǎn)耐心,先把它們放回口袋里,需要的時(shí)候再拿出來。這樣就可以用最恰當(dāng)?shù)姆绞绞褂盟鼈?。靈活性的提高可能以復(fù)雜性的提高為代價(jià)。比如說,給用戶提供一個(gè)可自定義配置的選擇提高了靈活性,但是因?yàn)槎嗔艘粋€(gè)配置文件,編程時(shí)也需要考慮這一點(diǎn),所以也就更復(fù)雜了。反過來簡單也可以促進(jìn)靈活。在前面的例子中,如果可以找到取消配置選項(xiàng)但又不喪失價(jià)值的方式,那么這個(gè)程序以后就更容易改動(dòng)。增進(jìn)軟件的溝通效果同樣會(huì)提高靈活性。能夠快速閱讀、理解和修改你的代碼的人越多,它將來發(fā)生變化的選擇就越多。本書中介紹的模式會(huì)通過幫助編程人員創(chuàng)建簡單、可以理解、可以修改的應(yīng)用程序來提高程序的靈活性。\h3.2原則實(shí)現(xiàn)模式并不是無緣無故產(chǎn)生的。每一種模式都或多或少體現(xiàn)了溝通、簡單和靈活這些價(jià)值觀。原則是另一個(gè)層次上的通用思想,比價(jià)值觀更貼近于編程實(shí)際,同時(shí)又是模式的基礎(chǔ)。我們有很多理由來檢查一下這些原則。正如元素周期表幫助人們發(fā)現(xiàn)了新的元素,清晰的原則也可以引出新的模式。原則可以解釋模式背后的動(dòng)機(jī),它是有普遍意義的。在對(duì)立模式間進(jìn)行選擇時(shí),最好的方式就是用原則來說話,而不是讓模式爭來爭去。最后,如果遇到從未碰到過的情況,對(duì)原則的理解可以充當(dāng)我們的向?qū)?。例如,假如要使用新的編程語言,我可以根據(jù)自己對(duì)原則的理解發(fā)展出有效的編程方式,不必盲目模仿現(xiàn)有的編程方式,更不用拘泥于在其他語言中形成的習(xí)慣(雖然可以用任何語言編寫FORTRAN風(fēng)格的代碼,但不該那么做)。對(duì)原則的充分理解使我能夠快速地學(xué)習(xí),即使在新鮮局面下仍然能夠一以貫之地符合原則。接下來的部分,我將為你講述隱藏在模式背后的原則。\h3.2.1局部化影響組織代碼結(jié)構(gòu)時(shí),要保證變化只會(huì)產(chǎn)生局部化影響。如果這里的一個(gè)變化會(huì)引出那里的一個(gè)問題,那么變化的代價(jià)就會(huì)急劇上升了。把影響范圍縮到最小,代碼就會(huì)有極佳的溝通效果。它可以被逐步深入理解,不必一開始就要鳥瞰全景。因?yàn)閷?shí)現(xiàn)模式背后一條最主要的動(dòng)機(jī)就是減少變化所引起的代價(jià),所以局部化影響這條原則也是很多模式的形成緣由之一。\h3.2.2最小化重復(fù)最小化重復(fù)這條原則有助于保證局部化影響。如果相同的代碼出現(xiàn)在很多地方,那么改動(dòng)其中一處副本時(shí),就不得不考慮是否需要修改其他副本;變動(dòng)不再只發(fā)生在局部。代碼的復(fù)制越多,變化的代價(jià)就越大。復(fù)制代碼只是重復(fù)的一種形式。并行的類層次結(jié)構(gòu)也是其一,同樣破壞了局部化影響原則。如果修改一處概念需要修改兩個(gè)或更多的類層次結(jié)構(gòu),就表示變化的影響已經(jīng)擴(kuò)散了。此時(shí)應(yīng)重新組織代碼,讓變化只對(duì)局部產(chǎn)生影響。這種做法可以有效改進(jìn)代碼質(zhì)量。重復(fù)不容易被預(yù)見到,有時(shí)在出現(xiàn)以后一段時(shí)間才會(huì)被覺察。重復(fù)不是罪過,它只是增加了變化的開銷。我們可以把程序拆分成許多更小的部分——小段語句、小段方法、小型對(duì)象和小型包,從而消除重復(fù)。大段邏輯很容易與其他大段邏輯出現(xiàn)重復(fù)的代碼片斷,于是就有了模式誕生的可能,雖然不同的代碼段落中存在差異,但也有很多相似之處。如果能夠清晰地表述出哪些部分程序是等同的,哪些部分相似性很少,而哪些部分則截然不同,程序就會(huì)更容易閱讀,修改的代價(jià)也會(huì)更小。\h3.2.3將邏輯與數(shù)據(jù)捆綁局部化影響的必然結(jié)果就是將邏輯與數(shù)據(jù)捆綁。把邏輯與邏輯所處理的數(shù)據(jù)放在一起,如果有可能盡量放到一個(gè)方法中,或者退一步,放到一個(gè)對(duì)象里面,最起碼也要放到一個(gè)包下面。在發(fā)生變化時(shí),邏輯和數(shù)據(jù)很可能會(huì)同時(shí)被改動(dòng)。如果它們被放在一起,那么修改它們所造成的影響就會(huì)只停留在局部。在編碼開始的那一刻,我們往往不太清楚該把邏輯和數(shù)據(jù)放到哪里。我可能在A中編寫代碼的時(shí)候才意識(shí)到需要B中的數(shù)據(jù)。在代碼正常工作之后,我才意識(shí)到它與數(shù)據(jù)離得太遠(yuǎn)。這時(shí)候我需要做出選擇:是該把代碼挪到數(shù)據(jù)那邊去,還是把代碼挪到邏輯這邊來,或者把代碼和數(shù)據(jù)都放到一個(gè)輔助對(duì)象中?也許還可能意識(shí)到,這時(shí)我還沒法找出如何組合它們以便增進(jìn)溝通的最好方式。\h3.2.4對(duì)稱性對(duì)稱性也是我隨時(shí)隨地運(yùn)用的一項(xiàng)原則。程序中處處充滿了對(duì)稱性。比如add()方法總會(huì)伴隨著remove()方法,一組方法會(huì)接受同樣的參數(shù),一個(gè)對(duì)象中所有的字段都具有相同的生命周期。識(shí)別出對(duì)稱性,把它清晰地表述出來,代碼將更容易閱讀。一旦閱讀者理解了對(duì)稱性所涵蓋的某一半,他們就會(huì)很快地理解另外一半。對(duì)稱性往往用空間詞匯進(jìn)行表述:左右對(duì)稱的、旋轉(zhuǎn)的,等等。程序中的對(duì)稱性指的是概念上的對(duì)稱,而不是圖形上的對(duì)稱。代碼中對(duì)稱性的表現(xiàn),是無論在什么地方,同樣的概念都以同樣的形式呈現(xiàn)。這是一個(gè)缺少對(duì)稱性的例子:voidprocess(){input();count++;output();}第二條語句比其他的語句更加具體。我會(huì)根據(jù)對(duì)稱性的原則重寫它,結(jié)果是:voidprocess(){input();incrementCount();output();}這個(gè)方法依然違反了對(duì)稱性。這里的input()和output()操作都是通過方法意圖來命名的,但是incrementCount()這個(gè)方法卻以實(shí)現(xiàn)方式來命名。在追求對(duì)稱性的時(shí)候,我會(huì)考慮為什么我會(huì)增加這個(gè)數(shù)值,于是就有了下面的結(jié)果:voidprocess(){input();tally();output();}在準(zhǔn)備消滅重復(fù)之前,常常需要尋找并表示出代碼中的對(duì)稱性。如果在很多代碼中都存在類似的想法,那么可以先把它們用對(duì)稱的方式表示出來,讓接下來的重構(gòu)有一個(gè)良好開端。\h3.2.5聲明式表達(dá)實(shí)現(xiàn)模式背后的另一條原則是盡可能聲明式地表達(dá)出意圖。命令式的編程語言功能強(qiáng)大靈活,但是在閱讀時(shí)需要跟隨著代碼的執(zhí)行流程。我必須在大腦中建起一個(gè)程序狀態(tài)、控制流和數(shù)據(jù)流的模型。對(duì)于那些只是陳述簡單事實(shí),不需要一系列條件語句的程序片斷,如果用簡單的聲明方式寫出來,讀著就容易多了。比如在JUnit的早期版本中,測試類里可能會(huì)有一個(gè)靜態(tài)的suite()方法,該方法會(huì)返回需要運(yùn)行的測試集合。publicstaticjunit.framework.Testsuite(){Testresult=newTestSuite();...complicatedstuff...returnresult;}現(xiàn)在就有了一個(gè)很簡單很常見的問題:哪些測試會(huì)被執(zhí)行?在大多數(shù)情況下,suite()方法只是將多個(gè)類中的測試匯總起來。但是因?yàn)樗且粋€(gè)通用方法,所以我必須要讀過、理解該方法以后,才能夠百分之百確定它的功能。JUnit4用了聲明式表達(dá)原則來解決這個(gè)問題。它不是用一個(gè)方法來返回測試集,而是用了一個(gè)特殊的testrunner來執(zhí)行多個(gè)類中的所有測試(這是最常見的情況):@RunWith(Suite.class)@TestClasses({SimpleTest.class,ComplicatedTest.class})classAllTests{}如果測試是用這種方式匯總的,那么我只需要讀一下TestClasses注解就可以知道哪些測試會(huì)被執(zhí)行。面對(duì)這種聲明式的表達(dá)方式,我不需要臆測會(huì)出現(xiàn)什么奇怪的例外情況。這個(gè)解決方案放棄了原始的suite()方法所具備的能力和通用性,但是它聲明式的風(fēng)格使得代碼更加容易閱讀。(在運(yùn)行測試方面,RunWith注解比suite()方法更為靈活,但這應(yīng)該是另外一本書里的故事了。)\h3.2.6變化率最后一個(gè)原則就是把具有相同變化率的邏輯、數(shù)據(jù)放在一起,把具有不同變化率的邏輯、數(shù)據(jù)分離。變化率具有時(shí)間上的對(duì)稱性。有時(shí)候可以將變化率原則應(yīng)用于人為的變化。例如,如果開發(fā)一套稅務(wù)軟件,我會(huì)把計(jì)算通用稅金的代碼和計(jì)算某年特定稅金的代碼分離開。兩類代碼的變化率是不同的。在下一年中做調(diào)整的時(shí)候,我會(huì)希望能夠確保上一年中的代碼依然奏效。分離兩類代碼可以讓我更確信每年的修改只會(huì)產(chǎn)生局部化影響。變化率原則也適用于數(shù)據(jù)。一個(gè)對(duì)象中所有成員變量的變化率應(yīng)該差不多是相同的。只會(huì)在一個(gè)方法的生命周期內(nèi)修改的成員變量應(yīng)該是局部變量。兩個(gè)同時(shí)變化但又和其他成員的變化步調(diào)不一致的變量可能應(yīng)該屬于某個(gè)輔助對(duì)象。比如金融票據(jù)的數(shù)值與幣種會(huì)同時(shí)變化,那么這兩個(gè)字段最好放到一個(gè)輔助對(duì)象Money中:setAmount(intvalue,Stringcurrency){this.value=value;this.currency=currency;}上面這段代碼就變成了:setAmount(intvalue,Stringcurrency){this.value=newMoney(value,currency);}然后進(jìn)一步調(diào)整:setAmount(Moneyvalue){this.value=value;}變化率原則也是對(duì)稱性的一個(gè)應(yīng)用,不過是時(shí)間上的對(duì)稱。在上面的例子中,value和currency這兩個(gè)初始字段是對(duì)稱的,它們會(huì)同時(shí)變化。但它們與對(duì)象中其他的字段是不對(duì)稱的。把它們放到自己應(yīng)該從屬的對(duì)象中,讓新的對(duì)象向閱讀者傳達(dá)出它們的對(duì)稱關(guān)系,這樣就更有可能在將來消除重復(fù),進(jìn)一步達(dá)到影響的局部化。\h3.3小結(jié)本章介紹了實(shí)現(xiàn)模式的理論基礎(chǔ)。溝通、簡單和靈活這三條價(jià)值觀為模式提供了廣泛的動(dòng)機(jī)。局部化影響、最小化重復(fù)、將邏輯與數(shù)據(jù)捆綁、對(duì)稱性、聲明式表達(dá)和變化率這6條原則幫助我們將價(jià)值觀轉(zhuǎn)化為實(shí)際行動(dòng)。接下來我們將會(huì)進(jìn)入模式的世界,看一看針對(duì)編程實(shí)戰(zhàn)中頻繁出現(xiàn)的問題,會(huì)有哪些特定的解決方案。注重通過代碼與人溝通是一件有價(jià)值的事情,我們將在下一章“動(dòng)機(jī)”中探尋其背后的經(jīng)濟(jì)因素。\h第4章動(dòng)機(jī)30年前,Yourdon和Constantine在StructuredDesign一書中將經(jīng)濟(jì)學(xué)作為了軟件設(shè)計(jì)的底層驅(qū)動(dòng)力。軟件設(shè)計(jì)應(yīng)該致力于減少整體成本。軟件成本costtotal可以被分解為初始成本costdevelop和維護(hù)成本costmaintain:costtotal=costdevelop+costmaintain當(dāng)這個(gè)行業(yè)在軟件開發(fā)的過程中慢慢積累了經(jīng)驗(yàn)以后,人們發(fā)現(xiàn),軟件的維護(hù)成本要遠(yuǎn)遠(yuǎn)高于它的初始成本。這個(gè)結(jié)果讓大多數(shù)人都倒吸了一口冷氣。(那些對(duì)維護(hù)的需求很小或者根本不需要維護(hù)的項(xiàng)目,它們所使用的模式應(yīng)該和本書所講述的實(shí)現(xiàn)模式迥異。)維護(hù)的代價(jià)很大,這是因?yàn)槔斫猬F(xiàn)有代碼需要花費(fèi)時(shí)間,而且容易出錯(cuò)。知道了需要修改什么以后,做出改動(dòng)就變得輕而易舉了。掌握現(xiàn)在的代碼做了哪些事情是最需要花費(fèi)人力物力的部分。改動(dòng)之后,還要進(jìn)行測試和部署。costmaintain=costunderstand+costchange+costtest+costdeploy減少整體成本的策略之一是在初期的開發(fā)中投入更多精力,希望借此減少甚至消除維護(hù)的需要。這些做法往往會(huì)失敗。一旦代碼以未預(yù)期的方式發(fā)生變化,人們所曾做出的任何預(yù)見都不再是萬全之策。人們可能會(huì)為了防備將來發(fā)生的變化而過早考慮代碼的通用性,但如果出現(xiàn)了沒有預(yù)料到而又勢(shì)在必行的變化,先前的做法往往就會(huì)與現(xiàn)實(shí)發(fā)生沖突。從本質(zhì)上看,增加軟件的先期投入是與兩條重要的經(jīng)濟(jì)學(xué)原則——金錢的時(shí)間價(jià)值和未來的不確定性——相悖的。今天的一元錢會(huì)比明天的一元錢更值錢,所以從原則上講,我們的實(shí)現(xiàn)策略應(yīng)該是盡量將支出推后。同樣,由于不確定性的存在,實(shí)現(xiàn)策略應(yīng)該更傾向于帶來即時(shí)收益而非長遠(yuǎn)收益。這聽上去好像在鼓勵(lì)人們目光短淺一些,不去考慮將來,但實(shí)際上這些實(shí)現(xiàn)模式一方面著眼于獲得即時(shí)收益,另一方面也在創(chuàng)建干凈的代碼,以方便將來的開發(fā)工作。我用來減少整體成本的策略是,要求所有開發(fā)人員在進(jìn)行維護(hù)的時(shí)候注重程序員與程序員之間的溝通,減少理解代碼所帶來的代價(jià)。清晰明確的代碼會(huì)帶來即時(shí)收益:代碼缺陷更少,更易共享,開發(fā)曲線更加平滑。將一些實(shí)現(xiàn)模式形成習(xí)慣后,我的開發(fā)速度得到了提升,令我分心的想法也更少了。剛開始寫下最初的幾個(gè)實(shí)現(xiàn)模式的時(shí)候(TheSmalltalkBestPracticePatterns,PrenticeHall,1996),我自以為是個(gè)編程能手。為了促使自己把注意力放在模式上,我在記錄下所遵守的模式之前一個(gè)字符的代碼也不肯輸入。那段經(jīng)歷實(shí)在是很折磨人,就像把手指扭結(jié)在一起打字一樣。在第一個(gè)星期內(nèi),每編寫一分鐘的程序都要先進(jìn)行幾個(gè)小時(shí)的寫作。到了第二個(gè)星期,我發(fā)現(xiàn)大多數(shù)的基本模式都已經(jīng)就緒了,大部分時(shí)間我只是在遵守這些現(xiàn)成的模式編程而已。還沒到第三個(gè)星期,我就比從前的開發(fā)速度快了很多,因?yàn)槲乙呀?jīng)認(rèn)認(rèn)真真地檢查過自己的開發(fā)方式,不會(huì)再有各種疑惑在我大腦中反復(fù)嘮叨干擾思路了。這些實(shí)現(xiàn)模式只有一部分是我自己的發(fā)明。我的開發(fā)方式有很大一部分都是從早一代程序員那里借鑒過來的。這些良好的編程習(xí)慣存在于那些容易閱讀、容易理解并容易維護(hù)的代碼之中,將它們落為明文以后,我的編碼速度得到了提升,也變得更加流暢。在為將來做好準(zhǔn)備的同時(shí),我還可以更快地完成今天的代碼。在編寫本書的過程中,我既總結(jié)了個(gè)人的編程習(xí)慣,也從已有的代碼中尋找靈感。我讀過JDK、Eclipse和我以往開發(fā)經(jīng)歷中的代碼,并將它們進(jìn)行了比較。最后所形成的這些模式,是想幫助讀者清晰地認(rèn)識(shí)到該如何編寫人們可以理解的代碼。關(guān)注的方向不同,價(jià)值觀念不同,就會(huì)形成不同的模式。比如在“改進(jìn)框架”一章中,我撰寫了專門適合開發(fā)框架的實(shí)現(xiàn)模式。開發(fā)框架時(shí)的價(jià)值取向不同于一般開發(fā),所以其實(shí)現(xiàn)模式也不同于一般的實(shí)現(xiàn)模式。就像為經(jīng)濟(jì)目的服務(wù)一樣,實(shí)現(xiàn)模式也在為人類服務(wù)。代碼來自于人,服務(wù)于人。編程人員可以使用實(shí)現(xiàn)模式來滿足人本身的需要,比如從工作中獲得成就感,或者成為社區(qū)中為人信任的一員。在后續(xù)的章節(jié)中,我會(huì)繼續(xù)討論模式給人和經(jīng)濟(jì)兩方面帶來的影響。\h第5章類類的概念早在柏拉圖之前就出現(xiàn)了。比如說,5種柏拉圖立體\h[1]就是5個(gè)類,它們的實(shí)例隨處可見。柏拉圖立體是絕對(duì)完美的,但它們并不實(shí)際存在。至于我們身邊那些觸手可及的實(shí)例,它們總有某些不甚完美的方面。面向?qū)ο缶幊滔癜乩瓐D之后的西方哲學(xué)家一樣延續(xù)了這種思維。面向?qū)ο缶幊贪殉绦騽澐殖稍S多類,類是對(duì)一組相似的東西的一般歸納,而對(duì)象則是這些東西本身。類對(duì)于溝通很重要,因?yàn)樗鼈兛梢悦枋龊芏嗑唧w的東西。實(shí)現(xiàn)模式最大的跨度只到類一級(jí);與之相比,設(shè)計(jì)模式則主要是在討論類與類之間的關(guān)系。本章將會(huì)介紹下列模式:類(Class)——用一個(gè)類來表示“這些數(shù)據(jù)應(yīng)該放在一起,還有這些邏輯應(yīng)該也和它們?cè)谝黄稹?;簡單的超類名(SimpleSuperclassName)——位于繼承體系根上的類應(yīng)該有簡單的名字,用以描繪它的隱喻;限定性的子類名(QualifiedSubclassName)——子類的名字應(yīng)該表達(dá)出它與超類之間的相似性和差異性;抽象接口(AbstractInterface)——將接口與實(shí)現(xiàn)分離;interface——用Java的interface機(jī)制來表現(xiàn)不常變化的抽象接口;有版本的interface(VersionedInterface)——引入新的子interface,從而安全地對(duì)interface進(jìn)行擴(kuò)展;抽象類(AbstractClass)——用抽象類來表現(xiàn)很可能變化的抽象接口;值對(duì)象(ValueObject)——這種對(duì)象的行為就好像數(shù)值一樣;特化(Specialization)——清晰地描述相關(guān)計(jì)算之間的相似性和差異性;子類(Subclass)——用一個(gè)子類表現(xiàn)一個(gè)維度上的變化;實(shí)現(xiàn)器(Implementor)——覆蓋一個(gè)方法,從而表現(xiàn)一種計(jì)算上的變化;內(nèi)部類(InnerClass)——將只在局部有用的代碼放在一個(gè)私有的類中;實(shí)例特有的行為(Instance-specificBehavior)——每個(gè)實(shí)例的邏輯都有不同;條件(Conditional)——明確指定條件,以表現(xiàn)不同的邏輯;委派(Delegation)——把操作委派給不同類型的對(duì)象,以表現(xiàn)不同的邏輯;可插拔的選擇器(PluggableSelector)——通過反射來調(diào)用方法,以表現(xiàn)不同的邏輯;匿名內(nèi)部類(AnonymousInnerClass)——在方法內(nèi)部創(chuàng)建一個(gè)新對(duì)象,并覆蓋其中的一兩種方法,以表現(xiàn)不同的邏輯;庫類(LibraryClass)——如果一組功能不適合放進(jìn)任何對(duì)象,就將其描述為一組靜態(tài)方法。\h5.1類數(shù)據(jù)的變化比邏輯要頻繁得多,正是這種現(xiàn)象讓類有了存在的意義。每個(gè)類其實(shí)就是這樣一個(gè)聲明:這些邏輯應(yīng)該放在一起,它們的變化不像它們所操作的數(shù)據(jù)那么頻繁;這些數(shù)據(jù)也應(yīng)該放在一起,它們變化的頻率差不多,并且由與之關(guān)聯(lián)的邏輯來負(fù)責(zé)處理。這種“數(shù)據(jù)會(huì)變、邏輯不變”的劃分并非絕對(duì)適用:有時(shí)隨著數(shù)據(jù)值的不同,邏輯也會(huì)有所不同;有時(shí)邏輯也會(huì)發(fā)生相當(dāng)大的變化;有時(shí)數(shù)據(jù)本身在計(jì)算的過程中反倒不會(huì)改變。學(xué)會(huì)如何用類來包裝邏輯和如何表達(dá)邏輯的變化,這是有效使用對(duì)象編程的重要部分。把多個(gè)類放進(jìn)一個(gè)繼承體系可以縮減代碼量,比原封不動(dòng)地把超類的內(nèi)容照抄到所有子類精簡得多。和所有縮減代碼量的技巧一樣,它也讓代碼變得更難讀懂;必須理解超類的上下文,然后才有可能理解子類。正確使用繼承也是有效使用對(duì)象編程的一方面。子類傳遞的信息應(yīng)該是:我和超類很像,只有少許差異。(我們經(jīng)常說在“子類”中“覆蓋”一種方法,這聽起來難道不奇怪嗎?要是當(dāng)初精心挑選一個(gè)好的隱喻,程序員的日子應(yīng)該好過得多吧。)在由對(duì)象搭建而成的程序中,類是相對(duì)昂貴的設(shè)計(jì)元素。一個(gè)類應(yīng)該做一些有直接而明顯的意義的事情。減少類的數(shù)量是對(duì)系統(tǒng)的改進(jìn),只要剩下的類不因此而變得臃腫就好。后面的模式介紹了如何通過類的聲明來表達(dá)設(shè)計(jì)思路。\h5.2簡單的超類名找到一個(gè)貼切的名字是編程中最令人開心的時(shí)刻之一。你一直為一個(gè)含糊不清的念頭而困擾,代碼變得越來越復(fù)雜,但你總覺得它可以不必那么復(fù)雜。然后,往往是在閑聊時(shí),有人冒了一句:“噢,我明白了,不就是個(gè)調(diào)度器(Scheduler)嗎?!庇谑谴蠹蚁蚝笠豢?,長舒一口氣。貼切的名字能引發(fā)連鎖反應(yīng),帶來更深入的簡化與改進(jìn)。在所有的命名當(dāng)中,類的命名是最重要的。類是維系其他概念的核心。一旦類有了名字,其中操作的名字也就順理成章了。相反的情況卻很少成立,除非類的名字一開始命名得太糟糕。類名的“簡短”與“表現(xiàn)力”之間存在張力。你會(huì)在交談中用到類名:“記得在平移Figure之前先要旋轉(zhuǎn)一下嗎?所以類名應(yīng)該簡明扼要,但有時(shí)候一個(gè)類名又要用到好幾個(gè)單詞才足夠精確。擺脫這種兩難境地的辦法就是給計(jì)算邏輯找到強(qiáng)有力的隱喻。腦子里有了隱喻,一個(gè)個(gè)單詞就不只是單詞,而是一張張關(guān)系、連接和暗示的大網(wǎng)。比如說在開發(fā)HotDraw這個(gè)繪圖框架時(shí),我一開始把圖畫(drawing)中的對(duì)象命名為DrawingObject。WardCunningham帶來了一個(gè)印刷方面的隱喻:一幅圖畫就好像印刷出來、排版妥當(dāng)?shù)募堩?,紙頁上畫出來的元素正是圖形(figure),于是這個(gè)類的名字就變成了Figure。有了這個(gè)隱喻作為鋪墊,F(xiàn)igure這個(gè)名字不僅比原來的DrawingObject更簡短,而且更準(zhǔn)確、更具表現(xiàn)力。有時(shí)候需要花些時(shí)間才能想出一個(gè)好名字。甚至可能代碼已經(jīng)“完工”,投入運(yùn)行了好幾周、好幾個(gè)月甚至(我真的遇到過這種情況)好幾年之后,突然想到了一個(gè)更好的類名。有時(shí)候需要強(qiáng)迫自己找到一個(gè)好名字,抽出一本辭典,寫下所有多少有些接近的名字,站起來走一走。另一些時(shí)候應(yīng)該帶著挫敗感和對(duì)時(shí)間的信心先去考慮新功能的實(shí)現(xiàn),潛意識(shí)會(huì)默默起作用的。交談總能幫助我想出更好的名字。要嘗試把一個(gè)對(duì)象的用途解釋給別人聽,我就得尋找具有表現(xiàn)力和感染力的圖景來描述它,這樣的圖景往往能引出新的名字。對(duì)于重要的類,盡量用一個(gè)單詞來為它命名。\h5.3限定性的子類名子類的名字有兩重職責(zé),不僅要描述這些類像什么,還要說明它們之間的區(qū)別是什么。同樣,在這里需要權(quán)衡長度與表現(xiàn)力。與位于繼承體系根上的超類不同,子類的名字在交談中用得并不頻繁,所以值得以犧牲簡明來換取更好的表現(xiàn)力。通常在超類名的基礎(chǔ)上擴(kuò)展一兩個(gè)詞就可以得到子類名。這條規(guī)則也有例外:如果繼承只是用作共享實(shí)現(xiàn)的機(jī)制,并且子類本身就代表一個(gè)重要的概念,那么這樣的子類就應(yīng)該被視為它自己的繼承體系的根,擁有一個(gè)簡單的名字。舉例來說,HotDraw里有一個(gè)Handle類,代表當(dāng)圖形被選中時(shí)對(duì)其進(jìn)行編輯操作。盡管它繼承自Figure類,它還是有一個(gè)簡單的名字:Handle。在它之下還有一大堆的子類,它們的名字也大多擴(kuò)展自Handle,例如StretchyHandle、TransparencyHandle等。由于Handle是這個(gè)繼承體系的根,因此它更應(yīng)該取一個(gè)簡單的超類名,而不是加上各種修飾語擴(kuò)展而成的子類名。給多級(jí)繼承體系中的子類命名也是一個(gè)難題。一般而言,多級(jí)繼承體系應(yīng)該進(jìn)行重構(gòu),換成使用委派,但既然它們還在這里,就應(yīng)該給它們一個(gè)好名字。不要不假思索地在直接超類的基礎(chǔ)上擴(kuò)展出子類名,要多從閱讀者的角度來想想閱讀者需要了解這個(gè)類的什么信息。你應(yīng)該帶著這個(gè)思考,以超類名為參考來給子類命名。與他人溝通是類名的用途,如果僅僅為了和計(jì)算機(jī)溝通,只要給每個(gè)類編號(hào)就足夠了。太長的類名讀寫都費(fèi)勁,太短的類名又會(huì)考驗(yàn)閱讀者的記憶力。如果一組類的名字體現(xiàn)不出它們之間的相關(guān)性,閱讀者就很難對(duì)它們形成整體印象,也很難回憶起它們的關(guān)系。應(yīng)該用類名來講述代碼的故事。\h5.4抽象接口請(qǐng)牢記軟件開發(fā)的古訓(xùn):針對(duì)接口編程,不要針對(duì)實(shí)現(xiàn)編程。從另一個(gè)角度來說,這也意味著設(shè)計(jì)決策不應(yīng)該暴露給不必要的地方。如果大部分代碼只知道我在處理一個(gè)容器,那么我就可以隨時(shí)改變這個(gè)容器的具體實(shí)現(xiàn)。但有時(shí)不得不指定具體類,否則計(jì)算就沒法進(jìn)行下去。這里所說的“接口”是指“一組沒有實(shí)現(xiàn)的操作”。在Java中,接口這個(gè)概念既可以表現(xiàn)為interface,也可以表現(xiàn)為超類。隨后的兩個(gè)模式會(huì)分別指出兩者的適用場景。每層接口都有成本:需要學(xué)習(xí)它,理解它,給它寫文檔,調(diào)試它,組織它,瀏覽它,還有給它命名。并不是接口數(shù)量越多軟件成本就會(huì)越少,只有需要接口帶來的靈活性時(shí)才值得為它付出成本。所以,既然很多時(shí)候并不能提前知道是否需要接口帶來的靈活性,出于降低成本的考慮,在仔細(xì)考慮“哪些地方需要接口”的同時(shí),最好是在真正需要這種靈活性時(shí)再引入接口。盡管我們成天都在抱怨軟件不夠靈活,但很多時(shí)候系統(tǒng)根本不需要變得更靈活。不管是要進(jìn)行基礎(chǔ)性的修改(例如改變整數(shù)類型的字節(jié)數(shù))還是大范圍的修改(例如引入新的商業(yè)模型),大部分軟件都不會(huì)需要最大限度的那種靈活性。在引入接口時(shí)的另一個(gè)經(jīng)濟(jì)方面的考量是軟件的不可預(yù)測性。我們這個(gè)行業(yè)似乎已經(jīng)沉溺于這樣一種觀念:只要一開始設(shè)計(jì)正確,軟件系統(tǒng)就不需要任何變動(dòng)。最近我讀到了一份關(guān)于“軟件變更的理由”的列表,其中列舉的條目包括程序員沒有弄清需求、客戶改變了想法,等等,唯一沒有提到的是正當(dāng)?shù)淖兏?。這樣的一份列表傳達(dá)出的信息是:變更總是錯(cuò)誤的。可是,為什么一份天氣預(yù)報(bào)不能永遠(yuǎn)正確呢?因?yàn)樘鞖庖圆豢深A(yù)測的方式變化。同樣的道理,為什么我們不能一次列出系統(tǒng)中所有需要靈活性的地方呢?因?yàn)樾枨蠛图夹g(shù)都在以不可預(yù)測的方式變化。這并非要給我們程序員免責(zé),我們?nèi)匀灰M全力開發(fā)客戶當(dāng)下需要的系統(tǒng);但它讓我們知道,通過預(yù)先思考來弄清軟件將來的樣子,其效果是相當(dāng)有限的。所有這些因素——對(duì)靈活性的需要、靈活性的成本、“何處需要靈活性”的不可預(yù)測——加在一起讓我相信:應(yīng)該在確定無疑地需要靈活性時(shí),才應(yīng)該引入這種靈活性。引入靈活性是有代價(jià)的,因?yàn)樾枰薷囊延械能浖?。如果不能?dú)自完成所有需要的修改,成本就會(huì)更高,我們?cè)诤竺骊P(guān)于“改進(jìn)框架”的章節(jié)中會(huì)詳細(xì)討論這個(gè)話題。Java有兩種方式來表現(xiàn)抽象接口:超類和interface。它們?cè)趹?yīng)對(duì)變化時(shí)涉及的成本各有不同。\h5.5interface要用Java表達(dá)“這是我要完成的任務(wù),除此之外的細(xì)節(jié)不歸我操心”,可以聲明一個(gè)erface是Java率先引入編程語言市場主流的重要?jiǎng)?chuàng)新之一。interface是一個(gè)很好的平衡,它帶來了多繼承的一部分靈活性,同時(shí)又沒有多繼承的復(fù)雜性和二義性。一個(gè)類可以實(shí)現(xiàn)多個(gè)erface只有操作,沒有成員變量,所以它們能夠有效保護(hù)其使用者不受實(shí)現(xiàn)變化的侵?jǐn)_。如果說interface讓改變的工作更加輕松,那么不能不提的就是對(duì)接口本身的修改是不被鼓勵(lì)的;一旦在interface上增加或者修改方法,就必須同時(shí)改變所有的實(shí)現(xiàn)類。如果無權(quán)改變實(shí)現(xiàn),大量使用interface會(huì)嚴(yán)重拖累日后的設(shè)計(jì)調(diào)整。此外interface的一個(gè)特點(diǎn)也影響了它們作為溝通手段的價(jià)值:其中所有的操作都必須是public的。我經(jīng)常會(huì)希望在interface中聲明一些包內(nèi)可見的操作。如果程序只是小范圍使用,讓設(shè)計(jì)元素的可見性略微高一點(diǎn)還不算什么大問題。但如果要把接口發(fā)布給很多人去用,那么最好是準(zhǔn)確地指定希望讓他們看到哪些操作。不要因?yàn)橐粫r(shí)懶惰斷了自己的后路。給interface命名有兩種風(fēng)格,選擇哪一種取決于如何看待它們。如果把interface看作“沒有實(shí)現(xiàn)的類”,那么就應(yīng)該像給類命名一樣地給它們命名(簡單的超類名、限定性的子類名)。這種命名風(fēng)格的問題是:接口會(huì)占掉那個(gè)最貼切的名字,給類命名的時(shí)候就不能用了。舉例來說,如果一個(gè)interface叫File,那么它的實(shí)現(xiàn)類就只好叫ActualFile、ConcreteFile或者(可惡?。〧ileImpl(后綴加縮寫)之類的。一般情況下,有必要讓使用者知道自己究竟是在操作具體的對(duì)象還是抽象的接口,至于這個(gè)“抽象的接口”究竟是interface還是超類倒是不那么要緊。用這種命名規(guī)則,可以不必一上來就把interface和超類劃清界限,于是你就可以在有必要時(shí)改變想法。但有時(shí)候比起隱藏“此處使用interface”這一事實(shí)來,具體類的命名對(duì)于交流更加重要。在這種情況下,可以給interface的名字加上“I”前綴:如果interface的名字是IFile,那么實(shí)現(xiàn)類就可以叫File了。\h5.6抽象類在Java中區(qū)分抽象接口與具體實(shí)現(xiàn)的另一種方式是使用超類。超類是抽象的,因?yàn)槌惖囊每梢栽谶\(yùn)行時(shí)替換為任何子類的對(duì)象;至于這個(gè)超類在Java的語法意義上是不是抽象的,這并不重要。何時(shí)應(yīng)該使用超類,何時(shí)應(yīng)該使用interface?取舍最終歸結(jié)為兩點(diǎn):接口會(huì)如何變化,實(shí)現(xiàn)類是否需要同時(shí)支持多個(gè)接口。抽象接口需要支持實(shí)現(xiàn)的變化以及接口本身的變化兩種類型的變化。Java的interface對(duì)后者的支持不佳;一旦改變interface,所有的實(shí)現(xiàn)類都必須同時(shí)修改。如果要修改一個(gè)有很多實(shí)現(xiàn)類的interface,很容易導(dǎo)致現(xiàn)有的設(shè)計(jì)陷入癱瘓,以致只好借助有版本的interface來調(diào)整設(shè)計(jì)。抽象類則沒有這方面的限制。只要提供了默認(rèn)實(shí)現(xiàn),在抽象類中新增的操作就不會(huì)侵?jǐn)_現(xiàn)有的實(shí)現(xiàn)類。抽象類的局限體現(xiàn)在實(shí)現(xiàn)類必須對(duì)其忠心不貳。如果需要以另一種視角來看待同一個(gè)實(shí)現(xiàn)類,就只能讓它實(shí)現(xiàn)interface了。用abstract關(guān)鍵字來修飾一個(gè)類可以告訴閱讀者:如果要使用這個(gè)類,就必須做一些實(shí)現(xiàn)工作。不過,只要有可能讓繼承體系根上的類被獨(dú)立創(chuàng)建和使用,就應(yīng)該這樣做。一旦走上抽象化這條路,就容易滑得太遠(yuǎn)而創(chuàng)造出沒有價(jià)值的抽象。努力讓繼承體系的根也能獨(dú)立創(chuàng)建,可以促使你消除那些不必要的抽象。interface和類繼承體系并不是互斥的。你可以提供一個(gè)接口說“你可以使用這些功能”,再提供一個(gè)超類說“這是一種實(shí)現(xiàn)方式”。此時(shí)使用者應(yīng)該引用接口類型,這樣未來的維護(hù)者就可以根據(jù)需要隨時(shí)替換新的實(shí)現(xiàn)。\h5.7有版本的interface如果想要修改一個(gè)interface但又不能修改,怎么辦?這種情況通常在想要增加操作時(shí)發(fā)生,在interface中增加操作會(huì)破壞所有現(xiàn)有的實(shí)現(xiàn)類,所以不能這樣做。不過可以聲明一個(gè)新的interface,使它繼承原來的interface,然后在其中增加操作。如果使用者需要新增的功能,就使用這個(gè)新的interface,其他使用者則繼續(xù)無視新interface的存在。使用這種做法,在需要新功能時(shí)必須明確檢查對(duì)象的類型,并將其向下轉(zhuǎn)型成新interface的類型。比如說,考慮一個(gè)簡單的“命令”interface:interfaceCommand{voidrun();}當(dāng)這個(gè)interface被發(fā)布出去并被實(shí)現(xiàn)了成千上萬次以后,修改它的成本就會(huì)極其高昂。但為了支持命令的取消,需要新增一個(gè)操作。用有版本的interface來解決這個(gè)問題,我們就得到了如下的子interface:interfaceReversibleCommandextendsCommand{voidundo();}所有現(xiàn)存的Command實(shí)例仍然照常工作,ReversibleCommand的實(shí)例也完全可以勝任Command的職責(zé)。如果需要使用新的操作,就需要向下轉(zhuǎn)型:...Commandrecent=...;if(recentinstanceofReversibleCommand){ReversibleCommanddowncasted=(ReversibleCommand)recent;downcasted.undo();}..一般情況下,使用instanceof會(huì)降低靈活性,因?yàn)檫@會(huì)把代碼與具體類綁定在一起。但在這個(gè)例子里使用instanceof應(yīng)該是合理的,因?yàn)檫@樣才能對(duì)interface作出調(diào)整。不過,如果可選的interface有很多,使用者就需要花很多精力來處理各種變化,這就表示你需要重新思考你的設(shè)計(jì)了。這是一種丑陋的解決方案,用來解決一類丑陋的問題。interface能很好地適應(yīng)實(shí)現(xiàn)的變化,卻不容易適應(yīng)自身結(jié)構(gòu)的變化。盡管如此,interface——和所有設(shè)計(jì)決策一樣——還是有可能會(huì)變化,畢竟我們都是在實(shí)現(xiàn)和維護(hù)的過程中學(xué)會(huì)設(shè)計(jì)的。通過提供有版本的interface,我們實(shí)際上創(chuàng)造了一種新的編程語言,看起來像Java,但規(guī)則卻是不同的。發(fā)明新語言是一個(gè)困難的游戲,這個(gè)游戲的規(guī)則比開發(fā)應(yīng)用程序要嚴(yán)苛得多。但無論如何,如果真的遇到需要對(duì)interface進(jìn)行擴(kuò)展的尷尬局面,知道該怎么去做總是好的。\h5.8值對(duì)象把具有可變狀態(tài)的對(duì)象作為思考計(jì)算問題的一種方式確實(shí)很有價(jià)值,但它并非唯一的方式。當(dāng)問題可以被歸約到由絕對(duì)真實(shí)和確定構(gòu)成的、在其中可以談?wù)摗坝篮阏胬怼钡某橄笫澜鐣r(shí),另一種思考這類問題的方式已經(jīng)發(fā)展了數(shù)千年,那就是數(shù)學(xué)。目前的編程語言則是兩種風(fēng)格的混合體。Java中所謂內(nèi)建類型(primitivetype)大多屬于數(shù)學(xué)領(lǐng)域:在Java里一個(gè)數(shù)加1實(shí)際上是一個(gè)數(shù)學(xué)操作(只除了某些人規(guī)定了我的計(jì)算機(jī)最多只能數(shù)到232或者264,然后就得從頭數(shù)起)。一個(gè)變量加1,變量本身的值不會(huì)改變,而是創(chuàng)建出一個(gè)新的值。你沒有辦法改變整數(shù)0,但對(duì)于其他大多數(shù)對(duì)象,你卻可以改變它們。這種函數(shù)式的計(jì)算風(fēng)格永遠(yuǎn)不會(huì)改變?nèi)魏螤顟B(tài),只是創(chuàng)建新的值。如果你面對(duì)一個(gè)可能只是暫時(shí)靜止的情景,隨后的操作或者查詢都針對(duì)這個(gè)情景來進(jìn)行,那么函數(shù)式的風(fēng)格比較合適。如果面對(duì)的情景總在不斷變化,那么有狀態(tài)的風(fēng)格比較合適。問題是,有些情景用兩種方式來看待都可以,那么又該如何選擇呢?比如說,對(duì)于“畫圖”這件事,可以把它表現(xiàn)為“圖介質(zhì)(例如位圖)的狀態(tài)變遷”,也可以用靜態(tài)的方式來描述這同一幅圖(如圖5.1所示)。圖5.1用過程的方式和對(duì)象的方式來描述圖哪種表現(xiàn)方式更有用?這一定程度上取決于個(gè)人偏好,但同時(shí)也取決于圖形的復(fù)雜程度,以及圖形發(fā)生變化的頻繁程度。過程式接口比函數(shù)式接口更常見。過程式接口的一個(gè)問題是,過程調(diào)用的順序成為了接口含義的重要(但往往并不明顯)組成部分。修改這樣的程序非常困難,需要格外小心,你可能只做了一點(diǎn)小小的改變,卻不留神改變了調(diào)用的順序,從而破壞了其中隱藏的接口含義,并因此帶來意料之外的影響。相比之下,數(shù)學(xué)表現(xiàn)方式的好處就在于調(diào)用順序?qū)涌诤x的影響很小。用這種編程方式,你就創(chuàng)造了一個(gè)微型世界,在其中可以作出絕對(duì)的、與時(shí)間無關(guān)的陳述。只要有可能,就應(yīng)該創(chuàng)造這種數(shù)學(xué)的微型世界,然后通過具有可變狀態(tài)的對(duì)象來管理它們。舉例來說,在一個(gè)財(cái)務(wù)系統(tǒng)中,我們可以把基本的交易表現(xiàn)為不可變的數(shù)學(xué)值。classTransaction{intvalue;Transaction(intvalue,Accountcredit,Accountdebit){this.value=value;credit.addCredit(this);debit.addDebit(this);}intgetValue(){returnvalue;}}一旦Transaction對(duì)象被創(chuàng)建出來,它的值就無法改變。而且它的構(gòu)造函數(shù)清楚地指出,任何交易都會(huì)同時(shí)記入借方和貸方賬戶。讀過這段代碼,我就知道自己不必?fù)?dān)心交易雙方的賬目對(duì)不上,或是在傳遞的過程中交易額發(fā)生改變。要實(shí)現(xiàn)一個(gè)值對(duì)象(或者說,看起來像是整數(shù)而不是像一個(gè)可變狀態(tài)的容器那樣的對(duì)象),需要首先在“狀態(tài)的世界”和“值的世界”之間畫出一條邊界。在上面的例子中,Transaction是值,而Account則包含可變的狀態(tài)。值對(duì)象的所有狀態(tài)都應(yīng)該在構(gòu)造器中設(shè)置,其他地方不再提供改變其內(nèi)部狀態(tài)的方式。對(duì)值對(duì)象的操作總是返回新的對(duì)象,操作的發(fā)起者要自己保存返回的對(duì)象。圖5.2狀態(tài)會(huì)變化的對(duì)象引用不可變的對(duì)象bounds.translateBy(10,20);//mutableRectanglebounds=bounds.translateBy(10,20);//value-styleRectangle對(duì)于值對(duì)象,最大的反對(duì)意見總是性能:創(chuàng)建那么多臨時(shí)對(duì)象,會(huì)讓內(nèi)存管理系統(tǒng)不堪重荷。但如果考慮整體成本,這種反對(duì)意見往往站不住腳,因?yàn)槌绦蛑械慕^大部分并不是性能瓶頸。其他不使用值對(duì)象的理由還包括不熟悉這種風(fēng)格,難以劃清系統(tǒng)中“狀態(tài)需要發(fā)生變化”與“對(duì)象不能變化”這兩部分的邊界,等等。需要注意,大部分是值風(fēng)格但又不純粹是值風(fēng)格的對(duì)象是最糟糕的,因?yàn)檫@種對(duì)象的接口會(huì)更復(fù)雜,而又不能保證它的狀態(tài)不會(huì)改變。盡管已經(jīng)說了那么多,但關(guān)于3種主要編程風(fēng)格(對(duì)象、函數(shù)式和過程式)及其有效應(yīng)用,我感覺還有千言萬語想要說。不過考慮到本書的目標(biāo),我只想最后再重復(fù)一遍,有時(shí)候,組合使用狀態(tài)可變的對(duì)象和像數(shù)值一樣不可變的對(duì)象,能夠最好地表現(xiàn)你的程序。\h5.9特化清晰地描述計(jì)算過程中相似性與差異性的相互作用,可以讓程序更容易閱讀、使用和修改。在實(shí)際工作中,沒有哪段程序是獨(dú)一無二的。不同程序會(huì)表達(dá)相似的概念,同樣,一個(gè)程序中的很多部分往往也在表達(dá)著相似的概念。清晰地描述相似性和差異性,就能讓閱讀者更好地理解現(xiàn)有的代碼,找出自己想要做的事情是否已經(jīng)被某種現(xiàn)有的各種實(shí)現(xiàn)所覆蓋,以及——如果還沒有現(xiàn)成實(shí)現(xiàn)——如何對(duì)現(xiàn)有代碼加以特化或是編寫新的代碼以滿足需要。最簡單的變化是狀態(tài)的差異。字符串“abc”顯然是與“def”不同的,但操作這兩個(gè)字符串的算法是一樣的,例如所有字符串的長度都以同樣的方法來計(jì)算。最復(fù)雜的變化是在邏輯上完全不同。符號(hào)積分子程序和數(shù)學(xué)式排版子程序在邏輯上毫無共通之處,雖然它們接受的輸入可能是一樣的。大多數(shù)程序位于兩個(gè)極端——“相同的邏輯處理不同的數(shù)據(jù)”和“不同的邏輯處理相同的數(shù)據(jù)”——之間的某個(gè)位置:數(shù)據(jù)可能大多相同,但略有些區(qū)別;邏輯可能大多相同,但略有些區(qū)別。(我猜即使符號(hào)積分子程序和數(shù)學(xué)式排版子程序也會(huì)多少共享一點(diǎn)代碼。)就連邏輯和數(shù)據(jù)之間的分界也有些模糊:一個(gè)標(biāo)記,它本身是boolean型的數(shù)據(jù),但會(huì)影響控制流的運(yùn)行;一個(gè)輔助對(duì)象可以被保存在成員變量里,但它又可以對(duì)計(jì)算的過程造成影響。隨后的幾個(gè)模式都是用于描述(主要是邏輯上的)相似性和差異性的技術(shù)。數(shù)據(jù)的變化似乎沒有那么復(fù)雜和微妙。有效地描述邏輯上的相似性和差異性,能為未來對(duì)代碼進(jìn)行擴(kuò)展創(chuàng)造新的機(jī)會(huì)。\h5.10子類聲明一個(gè)子類就是在說:這些對(duì)象與那些對(duì)象很類似,只除了……如果有個(gè)適當(dāng)?shù)某?,?chuàng)建子類會(huì)是一種強(qiáng)大的編程方式。通過覆蓋適當(dāng)?shù)姆椒?,只需幾行代碼就可以為現(xiàn)有的計(jì)算邏輯引入變化。對(duì)象技術(shù)剛開始流行時(shí),繼承一度被視為萬靈藥。一開始人們用繼承來分類:Train是Vehicle的子類,不管兩者是否共享任何實(shí)現(xiàn)。不久,一些人又發(fā)現(xiàn):既然繼承所做的就是共享實(shí)現(xiàn),用它來抽取共同的實(shí)現(xiàn)部分就是最有效的辦法了。但好景不長,繼承的局限性很快就暴露出來了。首先,這張牌只能用一次:如果事后發(fā)現(xiàn)一些變化情況無法用子類的方式很好地表達(dá),就得先花點(diǎn)工夫把代碼從繼承關(guān)系中解開,然后才能重新組織它。其次,使用者必須首先理解超類,然后才可能理解子類,隨著超類變得復(fù)雜,這個(gè)問題會(huì)更加嚴(yán)重。第三,對(duì)超類的修改頗有風(fēng)險(xiǎn),因?yàn)樽宇愑锌赡芤蕾囉诔悓?shí)現(xiàn)中某個(gè)微妙的屬性。最后,在過深的繼承體系中,所有這些問題都會(huì)出現(xiàn)。創(chuàng)建平行的繼承體系可以算是特別糟糕的繼承用法:“這個(gè)”繼承體系中的每個(gè)子類都需要“那個(gè)”繼承體系中的一個(gè)對(duì)應(yīng)的子類。這既是重復(fù)的一種形式,又在類繼承體系之間建立了隱晦的耦合。以后如果要引入一種新的變化情況,就要同時(shí)修改這兩個(gè)繼承體系。雖然我經(jīng)常會(huì)看到一些平行繼承體系而又一時(shí)想不出辦法來解決,但消除這種情況的努力確實(shí)會(huì)給設(shè)計(jì)帶來改善。圖5.3所示的保險(xiǎn)系統(tǒng)就是平行繼承體系的一個(gè)例子:這個(gè)設(shè)計(jì)必定有什么問題,因?yàn)镮nsuranceContract不可能引用PensionProduct,但把對(duì)Product對(duì)象的引用移到子類上也不是一個(gè)吸引人的方案。理想的解決辦法是把處理變化的邏輯換個(gè)地方:不管是處理保險(xiǎn)業(yè)務(wù)還是退休金業(yè)務(wù),Contract類都做同樣的事。這就需要新建一個(gè)類來代表預(yù)期的現(xiàn)金流(如圖5.4所示)。順便提一句:我們沒有真正到達(dá)這個(gè)方案所描述的設(shè)計(jì),不過在一年的奮斗之后我們已經(jīng)相當(dāng)接近了。圖5.3平行繼承體系圖5.4消除了繼承體系的重復(fù)只要記著所有這些警告,子類繼承還是一種用于表達(dá)計(jì)算的“基調(diào)與變奏”的強(qiáng)大工具。合適的子類能幫助人們用一兩個(gè)方法準(zhǔn)確地描述出自己想要的計(jì)算邏輯。要得到合適的子類,關(guān)鍵在于把超類中的邏輯進(jìn)行徹底地劃分,直到每個(gè)方法只做一件事。在編寫子類時(shí),應(yīng)該可以只覆蓋一個(gè)方法而不管其他方法。如果超類中的方法太多,就要把其中的代碼復(fù)制出來再加以修改(如圖5.5所示)。圖5.5在子類中復(fù)制超類的代碼再做修改復(fù)制過來的代碼為這兩個(gè)類帶來了丑陋而隱晦的耦合:不能放心地修改超類中的代碼,必須同時(shí)檢查甚至修改所有復(fù)制了這段代碼的地方。在做設(shè)計(jì)時(shí),我追求能著眼于眼前代碼的需要,隨心所欲地切換設(shè)計(jì)策略??紤]用條件語句、子類、委派等不同的方式來表現(xiàn)同樣的代碼。另一種策略會(huì)不會(huì)比現(xiàn)在使用的看起來更好?如果有這種感覺,那么朝這個(gè)方向走幾步試試看能不能對(duì)代碼有所改進(jìn)。子類繼承還有一個(gè)局限:它不能表現(xiàn)不斷變化的邏輯。你所要表現(xiàn)的變化情況在創(chuàng)建對(duì)象時(shí)就已經(jīng)清楚了,此后無法再改變。如果需要邏輯隨時(shí)變化,條件語句或是委派就能派上用場了。\h5.11實(shí)現(xiàn)器在由對(duì)象組成的程序中,多態(tài)消息是表達(dá)選擇的基本方法之一。為了讓消息能起到選擇的作用,能夠接收到該消息的對(duì)象就必須不止一種。把同一個(gè)協(xié)議實(shí)現(xiàn)多次(不管是用implements語法實(shí)現(xiàn)一個(gè)interface,還是用extends語法繼承一個(gè)類)所表達(dá)的意思是:從計(jì)算的這一方面來看,只要有某些符合代碼意圖的事情發(fā)生就可以了,至于“到底發(fā)生了什么”,我們并不關(guān)心。多態(tài)消息的優(yōu)美之處在于,它們給系統(tǒng)開啟了變化的機(jī)會(huì)。如果程序中的某一部分要把一些字節(jié)寫到另一個(gè)系統(tǒng),引入抽象的Socket就能讓開發(fā)者隨時(shí)改變套接字的具體實(shí)現(xiàn)而不影響其使用者。相比實(shí)現(xiàn)同樣功能的過程式實(shí)現(xiàn)(明確而封閉的條件邏輯),對(duì)象/消息的實(shí)現(xiàn)方式更加清晰,并且分離了意圖(傳遞一組字節(jié))與實(shí)現(xiàn)(用某些參數(shù)來進(jìn)行TCP/IP調(diào)用)。同時(shí),用對(duì)象和消息的方式來描述計(jì)算,讓系統(tǒng)有可能以最初的程序員做夢(mèng)都想不到的方式發(fā)生變化。清晰的表達(dá)與靈活性,兩者的天作之合正是面向?qū)ο笳Z言成為主流的原因所在。但很多人卻在用Java編寫過程式的程序,這不啻是暴殄天物。這里的模式就是為了幫助你更好地以一種清晰而又可擴(kuò)展的方式表達(dá)計(jì)算邏輯。\h5.12內(nèi)部類有時(shí)候需要把一部分計(jì)算邏輯包裝起來,但又不想新建一個(gè)文件來安置全新的類。這時(shí)可以聲明一個(gè)小的私有類(內(nèi)部類),這樣就可以低成本地獲得類的大部分好處。有時(shí)內(nèi)部類只需要繼承Object。有時(shí)它們會(huì)繼承別的超類,從而告訴閱讀者:超類的這種細(xì)微變體只在這個(gè)小范圍內(nèi)有意義。內(nèi)部類有一個(gè)特點(diǎn):當(dāng)內(nèi)部類被實(shí)例化時(shí),它的對(duì)象會(huì)悄悄地獲得創(chuàng)建它的那個(gè)對(duì)象。如果想訪問后者的實(shí)例數(shù)據(jù)而又不想在兩者之間建立顯式的關(guān)聯(lián),這個(gè)特點(diǎn)就顯得很方便了。publicclassInnerClassExample{privateStringfield;publicclassInner{publicStringexample(){returnfield;//Usesthefieldfromtheenclosinginstance}@Testpublicvoidpasses(){field="abc";Innerbar=newInner();assertEquals("abc",bar.example());}}但上面這個(gè)內(nèi)部類并沒有一個(gè)真正的無參構(gòu)造函數(shù),即便聲明一個(gè)也沒有用。所以嘗試通過反射來創(chuàng)建內(nèi)部類實(shí)例會(huì)遇到問題。pu
溫馨提示
- 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請(qǐng)下載最新的WinRAR軟件解壓。
- 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請(qǐng)聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
- 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內(nèi)容里面會(huì)有圖紙預(yù)覽,若沒有圖紙預(yù)覽就沒有圖紙。
- 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
- 5. 人人文庫網(wǎng)僅提供信息存儲(chǔ)空間,僅對(duì)用戶上傳內(nèi)容的表現(xiàn)方式做保護(hù)處理,對(duì)用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對(duì)任何下載內(nèi)容負(fù)責(zé)。
- 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請(qǐng)與我們聯(lián)系,我們立即糾正。
- 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時(shí)也不承擔(dān)用戶因使用這些下載資源對(duì)自己和他人造成任何形式的傷害或損失。
最新文檔
- 宗教場所裝修終止合同范本
- 二零二五年度養(yǎng)老設(shè)施面積補(bǔ)差及養(yǎng)老服務(wù)補(bǔ)充協(xié)議
- 2025年度網(wǎng)絡(luò)安全合同管理制度及流程保障
- 2025年度住宅小區(qū)物業(yè)漏水緊急賠償協(xié)議書
- 2025年度絕交協(xié)議范本:理性處理情感糾紛的協(xié)議樣本
- 2025-2030年中國棉布遮陽棚項(xiàng)目投資可行性研究分析報(bào)告
- 2025年夾圈絨行業(yè)深度研究分析報(bào)告-20241226-175543
- 2025年度商品房團(tuán)購合作協(xié)議書
- 2025年度互聯(lián)網(wǎng)醫(yī)療股份分配協(xié)議書模板
- 二零二五年度肉牛養(yǎng)殖與肉類加工廠訂單定制銷售協(xié)議
- 二零二五年度港口碼頭安全承包服務(wù)協(xié)議4篇
- 2024年蘇州衛(wèi)生職業(yè)技術(shù)學(xué)院高職單招語文歷年參考題庫含答案解析
- 《歡樂運(yùn)動(dòng)會(huì):1 我為班級(jí)出把力》說課稿-2024-2025學(xué)年四年級(jí)上冊(cè)綜合實(shí)踐活動(dòng)滬科黔科版
- 2024年南京機(jī)電職業(yè)技術(shù)學(xué)院單招職業(yè)技能測試題庫標(biāo)準(zhǔn)卷
- 2025年中智集團(tuán)及下屬單位招聘筆試參考題庫含答案解析
- 廣東2025年高中化學(xué)學(xué)業(yè)水平考試模擬試卷試題(含答案詳解)
- 2024年中國牛排2市場調(diào)查研究報(bào)告
- 2025年事業(yè)單位考試(綜合管理類A類)綜合應(yīng)用能力試題及解答參考
- 科創(chuàng)板知識(shí)題庫試題及答案
- UL1450標(biāo)準(zhǔn)中文版-2019電動(dòng)空氣壓縮機(jī)真空泵和涂裝設(shè)備中文版第四版
- 物業(yè)社區(qū)文化活動(dòng)培訓(xùn)
評(píng)論
0/150
提交評(píng)論