版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請進(jìn)行舉報或認(rèn)領(lǐng)
文檔簡介
編程語言實現(xiàn)模式
(中文版)
目錄
第1章引言
1.1章節(jié)概覽
1.2那么,現(xiàn)在……
第2章模式
第3章一種編程理論
3.1價值觀
3.1.1溝通
3.1.2簡單
3.1.3靈活
3.2原則
3.2.1局部化影響
3.2.2最小化重復(fù)
3.2.3將邏輯與數(shù)據(jù)捆綁
3.2.4對稱性
3.2.5聲明式表達(dá)
3.2.6變化率
3.3小結(jié)
笫4章動機(jī)
第5章類
5.1類
5.2簡單的超類名
5.3限定性的子類名
5.4抽象接口
5.5interface
5.6抽象類
5.7有版本的interface
5.8值對象
5.9特化
5.10子類
5.11實現(xiàn)器
5.12內(nèi)部類
5.13實例特有的行為
5.14條件語句
5.15委派
5.16可插拔的選擇器
5.17匿名內(nèi)部類
5.18庫類
5.19小結(jié)
第6章狀態(tài)
6.1狀態(tài)
6.2訪問
6.3直接訪問
6.4間接訪問
6.5通用狀態(tài)
6.6可變狀態(tài)
6.7外生狀態(tài)
6.8變量
6.9局部變量
6.10字段
6.11參數(shù)
6.12收集參數(shù)
6.13可選參數(shù)
6.14變長參數(shù)
6.15參數(shù)對象
6.16常量
6.17按角色命名
6.18聲明時的類型
6.19初始化
6.20及早初始化
6.21延遲初始化
6.22小結(jié)
第7章行為
7.1控制流
7.2主體流
7.3消息
7.4選擇性消息
7.5雙重分發(fā)
7.6分解性(序列性)消息
7.7反置性消息
7.8邀請性消息
7.9解釋性消息
7.10異常流
7.11衛(wèi)述句
7.12異常
7.13已檢查異常
7.14異常傳播
7.15小結(jié)
第8章方法
8.1組合方法
8.2揭示意圖的名稱
8.3方法可見性
8.4方法對象
8.5蒞蓋方法
8.6重載方法
8.7方法返回類型
8.8方法注釋
8.9助手方法
8.10調(diào)試輸出方法
8.11轉(zhuǎn)換
8.12轉(zhuǎn)換方法
8.13轉(zhuǎn)換構(gòu)造器
8.14創(chuàng)建
8.15完整的構(gòu)造器
8.16工廠方法
8.17內(nèi)部工廠
8.18容器訪問器方法
8.19布爾值設(shè)置方法
8.20查詢方法
8.21相等性判斷方法
8.22取值方法
8.23設(shè)置方法
8.24安全復(fù)制
8.25小結(jié)
第9章容器
9.1隱喻
9.2要點
9.3接口
9.3.1Array
9.3.2Iterable
9.3.3Collection
9.3.4List
9.3.5Set
9.3.6SortedSet
9.3.7Map
9.4實現(xiàn)
9.4.1Collection
9.4.2List
9.4.3Set
9.4.4Map
9.5Collections
9.5.1查詢
9.5.2排序
9.5.3不可修改的容器
9.5.4單元素容器
9.5.5空容器
9.6繼承容器
97小結(jié)
第10章/進(jìn)框架
10.1修改框架而不修改應(yīng)用
10.2不兼容的更新
10.3鼓勵可兼容的變化
10.3.1程序庫類
10.3.2對象
10.4小結(jié)
附錄A)生能度量
A.1示例
A.2API
A.3實現(xiàn)
A.4MethodTimer
A.5沖抵額外開銷
A.6測試
A.6.1容器的比較
A.6.2ArrayList和LinkedList的比較
A.6.3Set之間的比較
A.6.4Map之間的比較
A.7小結(jié)
第1章引言
現(xiàn)在開始吧。你選中了我的書(現(xiàn)在它就是你的了),你也編寫過代碼,很可能你已經(jīng)
從自己的經(jīng)驗中建立了一套屬于自己的風(fēng)格。
這本書的目標(biāo)是要幫助你通過代碼表達(dá)自己的意圖。首先,我們對編程和模式做一個概
述(第2章?第4章)。接下來(第5章?第8章)用一系列短文和模式,講述了“如何
用Java編寫出可讀的代碼”,如果你正在編寫框架(而不是應(yīng)用程序),最后一章會告
訴你如何調(diào)整前面給出的建議c總而言之,本書關(guān)注的焦點是用編程技巧來增進(jìn)溝通。
用代碼來溝通有幾個步驟。首先,必須在編程時保持清醒,第一次開始記錄實現(xiàn)模式時,
我編程已經(jīng)有一些年頭廣。我驚訝地發(fā)現(xiàn),盡管能夠快捷流暢地作出各種編程中的決定,
但我沒法解釋自己為什么如此前定諸如“這個方法為什么應(yīng)女被這樣調(diào)用”或者“那塊代
碼為什么屬于那個對象”之類的事情。邁向溝通的第一步就是讓自己慢下來,弄明白自己
究竟想了些什么,不再假裝自己是在憑本能編程。
第二步是要承認(rèn)他人的重要性。我很早就發(fā)現(xiàn)編程是一件令人滿足的事,但我是個以自
我為中心的人,所以必須學(xué)會相信其他人也跟我一樣重要,然后才能寫出能與他人溝通的
代碼。編程很少會是一個人與一臺機(jī)器之間孤獨(dú)的交流,我們應(yīng)該學(xué)會關(guān)心其他人,而這
需要練習(xí)。
所以我邁出了第三步。一旦把自己的想法暴露在光天化日下,并且承認(rèn)別人也有權(quán)和我
一樣地存在,我就必須實實在在地展示自己的新觀點了。我變用本書中介紹的實現(xiàn)模式,
目的是更有意識地編程,為他人編程,而不僅僅是為自己編程。
你當(dāng)然川以僅為其中的技術(shù)內(nèi)容一一有用的技巧和解釋一一血閱讀本書,但我認(rèn)為應(yīng)該
預(yù)先提醒你,除了這些技術(shù)內(nèi)容,本書還包含了很多東西,至少對我而言是這樣。
這些技術(shù)內(nèi)容都可以在介紹模式的章節(jié)里找到。學(xué)習(xí)這部分內(nèi)容有一個高效的策略:需
要用的時候再去讀。如果用這種“just-in-time”的方式來讀,那么可以直接跳到第5章,
把后續(xù)的章節(jié)快速掃一遍,然后在編程時把本書放在手邊。用過書中的很多模式之后,你
可以重新回到前面介紹性的內(nèi)容中來,讀一下那些技巧背后的道理。如果有興趣透徹理解
手上的這本書,也可以細(xì)細(xì)地從頭讀到尾。和我寫過的大部分書不同,這本書的每一章都
相當(dāng)長,因此在閱讀時要保持專注才行。
書中的大部分內(nèi)容都以模式的形式加以組織。編程中需要做的抉擇大多曾經(jīng)出現(xiàn)過。
個程序員的職業(yè)生涯中可能要給上百萬個變量命名,不可能每次都用全新的方式來命名。
命名的普遍約束總是一致的:需要把變量的用途、類型和生命周期告訴給閱讀者,需要挑
選一個容易讀懂的名字,需要挑選?個容易寫、符合標(biāo)準(zhǔn)格式.的名字。把這些普遍約束加
諸一個具體的變量之上,然后就得到了一個合用的名字?!敖o變量命名”就是一個模式:
盡管每次都可能創(chuàng)造出不同的名字,但決策的方法和約束條件總是重復(fù)出現(xiàn)的。
我覺得,模式需要以多種形式來呈現(xiàn),有時一篇充滿爭議的文章能最好地闡釋一個模式,
有時候是一幅圖,有時候是一個故事,有時候是一段示例。所以我并沒有嘗試把所有模式
都塞進(jìn)同一種死板的格式里,而是以我認(rèn)為最恰當(dāng)?shù)姆绞絹砻枋鏊鼈儭?/p>
書中總共包含了77個明確命名的模式,它們分別涵蓋了“編寫可讀的代碼”這件事的
某一方面。此外我還在書中提到了很多更小的模式或是模式的變體。我寫這本書的目的是
給程序員們一點建議,告訴他們?nèi)绾卧诿刻熳钇椒驳木幊倘蝿?wù)中幫助將來的閱讀者理解代
碼的意圖。
本書的深度應(yīng)該介于DesignPatterns(中譯版《設(shè)計模式:可復(fù)用面向刈象軟件的基
礎(chǔ)》)和Java語言手冊之間cDesignPatterns討論的是開發(fā)過程中每天要做幾次的那
種決策,通常是關(guān)于如何協(xié)調(diào)對象之間交互的決策,實現(xiàn)模式的粒度更小。編程時每過兒
秒鐘就可能用上一個模式。語言手冊很好地介紹了“能用Java做什么”,但對于“為什
么使用某種結(jié)構(gòu)”或者“別人讀到這段代碼時會怎么想”這徉的問題談?wù)撋跎?,而這正是
實現(xiàn)模式的重點所在。
在寫這本書時,我的一個原則就是只寫我熟悉的主題。比如說并發(fā)(concurrency)問
題就沒有涵蓋在這些實現(xiàn)模式中,并非因為并發(fā)不重要,只是因為我對這個主題沒有太多
可說的。我對待并發(fā)問題的策略一向很簡單:盡可能地把涉及并發(fā)的部分從我的程序中隔
離出去。雖然我一直用這個辦法還干得不錯,但這確實沒有多少可解釋的。更多關(guān)于并發(fā)
處理的實踐指導(dǎo),我推薦諸如JavaConcurrencyinPractice(中譯版《Java并發(fā)編程
實踐》)之類的書籍。
本書完全沒有涉及的另一個主題是軟件過程。我給出的建議只是告訴閱讀者如何用代碼
來交流,不管這代碼是在一個漫長流程的最后階段編寫出來的,還是在編寫出一個無法通
過的測試之后立即編寫出來的,我希望這些建議都同樣適用??偠灾还芄谝栽鯓拥?/p>
名目,只要能降低軟件開發(fā)的成本就是好事。
此外本書也盡量避免使用Java的最新特性。我在選擇技術(shù)時總是傾向于保守,因為無
所不用其極地嘗試新技術(shù)已經(jīng)傷害過我太多次了(作為學(xué)習(xí)新技術(shù)的策略,這很好;但對
于大部分開發(fā)工作而言,風(fēng)險太大)。所以,你會在本書中看到一個非常樸實的Java子
集。如果希望使用Java的最新特性,可以從別的地方去學(xué)習(xí)它們。
1.1章節(jié)概覽
本書總共分成了7大塊,如圖1.1所示,分別是:
總體介紹(Introduction)——這幾個簡短的章節(jié)描述了“用代碼溝通”的重要性與價
值所在,以及實現(xiàn)模式背后的思想;
類(Class)——這部分的模式講述了為什么要創(chuàng)建類,如何創(chuàng)建類,如何用類來書寫
邏輯等問題;
狀態(tài)(State)---關(guān)于狀態(tài)存取的模式;
行為(Behavior)——這部分的模式告訴閱讀者如何用代碼來表現(xiàn)邏輯,特別是如何用
多種不同的方式來做這件事;
方法(Mcthc#)一一關(guān)于如何編寫方法的模式,它們將告訴你,根據(jù)你對方法的分解
和命名,閱讀者會作出怎樣的判斷;
容器(Collections)一一關(guān)于選擇和使用容器的模式;
改進(jìn)框架(Frameworks)——上述模式的變體,適用于框架開發(fā)(而非應(yīng)用程序開發(fā))。
工t/TRoDUmoW
?paft^ns
?values,principles
?motWa+ion
CLASS強(qiáng)飛
SrAT£:~.
G飛"
CX>〃£C77O“S
樂WEUiORKS
圖L1全書概覽
1.2那么,現(xiàn)在
該言歸正傳了。如果你打算按部就班地讀下去,請翻到下一頁(我猜這用不著特別提
醒)。如果想快速瀏覽所有的模式,請從笫5章開始。
第2章模式
編程中的很多決策是無法復(fù)制的。開發(fā)網(wǎng)站的方式與開發(fā)心臟起搏器控制軟件的方式肯
定迥然不同。但決策的內(nèi)容越接近純技術(shù)化,其中的相似性就越多,我不是剛編寫過一樣
的代碼嗎?程序員為不斷重復(fù)的瑣事耗費(fèi)的時間越少,他們就有越多的時間來解決好真正
獨(dú)一無二的問題,從而更高效地編程。
絕大多數(shù)程序都遵循一組簡單的法則。
更多的時候,程序是在被閱讀,而不是被編寫。
沒有“完工”一說。修改程序的投入會遠(yuǎn)大于最初編與程序的投入。
程序都由一組基本的語句和控制流概念組合而成。
程序的閱讀者需要理解程序一一既從細(xì)節(jié).匕也從概念.匕有時他們從細(xì)節(jié)開始,逐漸
理解概念;有時他們從概念開始,逐漸理解細(xì)節(jié)。
模式就是基于這樣的共性之二的。比如說,每個程序員都必須決定如何進(jìn)行迭代遍歷。
在思考如何寫出循環(huán)的時候,大部分領(lǐng)域問題都被暫時拋在腦后了,留下的就是純技術(shù)問
題:這個循環(huán)應(yīng)該容易讀懂,容易編寫,容易驗證,容易修改,而且高效。
讓你操心的這一系列事情,就是模式的源起。上面列出的這些約束,或者叫壓力
(force),會影響程序中每個循環(huán)編寫的方式。可以預(yù)見到,這樣的壓力會不斷重現(xiàn),
這也正是模式之所以成為模式的原因:它其實是關(guān)于壓力的噢式。
有好幾種合理的方式可以寫出一個循環(huán),它們分別暗含著對這些約束不問的優(yōu)先級排序:
如果性能更重要,你可能用這種方式來寫循環(huán);如果容易修改更重要,你就可能用另一種
方式來寫循環(huán)。
每個模式都代表著一種對壓力進(jìn)行相對優(yōu)先級排序的觀點.大部分模式都由一篇短文來
描述,其中列舉出解決某一問題的各種方案,以及推薦方案的優(yōu)點所在。這些模式不僅給
出一個建議,而且還講出背后的原因,這樣閱讀者就可以自己判斷應(yīng)該如何解決這類重復(fù)
出現(xiàn)的問題。
正如前面暗示的,每個模式也都帶著一個解決方案的種子C關(guān)于“循環(huán)遍歷一個容器”
的模式可能會建議說“使用Java5的for循環(huán)來描述遍歷操作”。模式在抽象的原則和
具體的實踐之間架起了一座橋梁。模式可以幫助你編寫代碼。
模式彼此協(xié)作。建議你用for循環(huán)的模式,又引出了“如何給循環(huán)變量命名”的問題。
我們不嘗試把所有事情都塞進(jìn)一個模式里,還有另一個模式專門講“如何給變量命名”的
話題。
模式在本書中有多種不同的展現(xiàn)形式:有時它們有清晰的名稱,還有專門的章節(jié)來討論
壓力和解決方案。但也有時,一些比較小的模式就直接放在更大的模式內(nèi)部來介紹,一兩
句話或許就能夠把一個小模式討論清楚了。
使用模式有時會讓你感到束手束腳,但確實可以幫你節(jié)省時間和精力。打個比方,就好
像鋪床這件小事,如果每次都必須思考每個步驟怎么做、找出正確的順序,那就會比習(xí)慣
成自然的做法耗費(fèi)更多的精力c正是因為有一組鋪床的模式,這件事情才得以大大簡化。
如果床恰好頂在墻邊,或者床單太小,你會根據(jù)情況調(diào)整策咯,但整體來說還是遵循固定
模式來鋪床,這樣你的腦子就口J以用來思考更有意思、更有必要的東西。編程也是一樣,
當(dāng)模式成為習(xí)慣之后,我很開心地發(fā)現(xiàn)自己不必再為“如何寫一個循環(huán)”而展開討論了。
如果整個團(tuán)隊都對一個模式不漏,那么他們可以討論引入新的模式。
沒有任何一組模式能夠適用于所有情況。本書中列出的模式是我在應(yīng)用程序開發(fā)中親自
用過的,或者看到別人用過并目.效果不錯的(后文也淺談了一下框架開發(fā)中的模式)。盲
目效仿別人的風(fēng)格,永遠(yuǎn)都不如思考和實踐自己的風(fēng)格并在團(tuán)隊中討論交流來得有效。
模式最大的作用就是幫助人們做決定。有些實現(xiàn)模式最終會融入編程語言,就好像
setjmp()/longjmp()結(jié)構(gòu)變成了如今的異常處理。不過大部分時候,模式需要加以調(diào)整
才能投入使用。
從這一章開始,我們試圖尋找一種更節(jié)約、更快速、更省力的方式來解決常見的編程問
題。使用模式可以幫助程序員用更合理的方式來解決常見問題,從而把更多的時間、精力
和創(chuàng)造力留下來解決真正獨(dú)一無二的問題。每個模式都涉及?個常見的編程問題,隨后我
們會討論其中起影響作用的各種因素,并提出具體的建議:如何快速實現(xiàn)一個令人滿意的
解決方案。其結(jié)果是,這些模式將幫助讀者更好、更快、更省力地完成編程工作中乏味的
部分,從而留下更多的時間和精力來解決程序中獨(dú)一無二的問題。
本書中的實現(xiàn)模式共同構(gòu)筑了一種編程風(fēng)格,下一章“一種編程理論”將會介紹這種編
程風(fēng)格背后的價值觀和原則。
第3章一種編程理論
就算是再巨細(xì)靡遺的模式列表,也不可能涵蓋編程中所遇到的每一種情況。你免不了
(甚至常常)會遭遇到這種情景:上窮碧落,也找不到對應(yīng)的現(xiàn)成解決方案。于是便需要
有針對特定問題的通用解決方案。這也正是學(xué)習(xí)編程理論的原因之一。原因之二則是那種
知曉如何去做、為何去做之后所帶來的胸有成竹。當(dāng)然,如果把編程的理論和實踐結(jié)合起
來討論,內(nèi)容就會更加精彩了C
每個模式都承載著一點點理論。但實際編程中存在一些更加深廣的影響力,遠(yuǎn)不是孤立
的模式所能概括的。本章將會講述這些貫穿于編程中的橫切概念,它們分為兩類:價值觀
與原則。價值觀是編程過程的統(tǒng)一支配性主題。珍視與其他人溝通的重要性,把代碼中多
余的復(fù)雜性去掉,并保持開放的心態(tài),這才是我工作狀態(tài)最佳的表現(xiàn)。這些價值觀一一溝
通、簡單和靈活一一影響r我在編程時所做的每個決策。
此處描述的原則不像上面的價值觀那樣意義深遠(yuǎn),不過每一項原則都在許多模式中得以
體現(xiàn)。價值觀有普遍的意義,但往往難以直接應(yīng)用;模式雖可以直接應(yīng)用,卻是針對于特
定情景;原則在價值觀和模式之間搭建了橋梁。我早已發(fā)現(xiàn),在那種沒有模式可以應(yīng)用,
或是兩個相互排斥的模式可以同等應(yīng)用的場合,如果把編程原則弄清楚,對解決疑難會是
一件好事。在面對不確定性的時候,對原則的理解讓我可以“無中生有”創(chuàng)造出一些東西,
同時能和其他的實踐保持一致,而且結(jié)果一般都不錯。
價值觀、原則和模式,這3種元素互為補(bǔ)充,組成了一種穩(wěn)定的開發(fā)方式。模式描述了
要做什么,價值觀提供了動機(jī),原則把動機(jī)轉(zhuǎn)化成了實際行動。
這里的價值觀、原則和模式,是通過我的親身實踐、反思以及與其他人的討論總結(jié)出來
的。我們都曾經(jīng)從前人那里吸收經(jīng)驗,最終會形成一種開發(fā)方式,但不是唯一的開發(fā)方式。
不同的價值觀和不同的原則會產(chǎn)生不同的方式。把編程方式用價值觀、原則和模式的形式
展現(xiàn)出來,其優(yōu)點之一就是可以更加有效地展現(xiàn)編程方法的差異。如果你喜歡用某種方式
來做事,而我喜歡另一種,那么就可以識別出我們在哪種層次上存在分歧,從而避免浪費(fèi)
時間。如果我們各自認(rèn)可不同的原則,那么爭論哪里該用大括號根本無助于解決問題。
3.1價值觀
有3個價值觀與卓越的編程血脈相連,它們分別是:溝通、簡單和靈活。雖然它們有時
候會有所沖突,但更多的時候則是相得益彰。最優(yōu)秀的程序會為未來的擴(kuò)展留卜.充分的選
擇余地,不包含不相關(guān)的元素,容易閱讀,容易理解。
3.1.1溝通
如果閱讀者可以理解某段代碼,并且進(jìn)一步修改或使用它,那么這段代碼的溝通效果就
很好。在編程時,我們很容易從計算機(jī)的角度進(jìn)行思考。但只有一面編程一面考慮其他人
的感受,我才能編寫出好的代瑪。在這種前提下編寫出的代碼更加干凈易讀,更有效率,
更清晰地展現(xiàn)出我的想法,給了我全新的視角,減輕了我的壓力。我的一些社會性需要得
到了自我滿足。最開始編程吸引我的部分原因在于我可以通過編程與外界交流,然而,我
不想與那些難纏又無法理喻的煩人家伙打交道。過了20年,把別人當(dāng)作空氣一樣的編程
方式才在我眼中褪盡了顏色。耗盡心神去精心搭建一座糖果城堡,于我而言已毫無意義。
Knuth所提出的文學(xué)編程理論促使我把注意力放到溝通上來:程序應(yīng)該讀起來像一本書
?樣。它需要有情節(jié)和韻律,句子間應(yīng)該有優(yōu)雅的小小跌宕起伏。
我和WardCunningham第一次接觸到文學(xué)性程序這個概念以后,我們決定來試一試。我
們找出Smalltalk中最干凈的代碼之-----ScrollController,坐到一起,然后試著把它
寫成一個故事。幾個小時以后,我們以自己的方式完全重寫了這段代碼,把它變成了一篇
合情合理的文章。每次遇到難以解釋清楚的邏輯,重新把它寫一遍都要比解釋這段代碼為
何難以理解容易得多。溝通的需要改變了我們對于編碼的看法。
在編程時注重溝通還有一個很明顯的經(jīng)濟(jì)學(xué)基礎(chǔ)。軟件的絕大部分成本都是在第一次部
署以后才產(chǎn)生的。從我自己修改代碼的經(jīng)驗出發(fā),我花在閱讀既有代碼上的時間要比編寫
全新的代碼長得多。如果我想減少代碼所帶來的開俏,我就應(yīng)該讓它容易讀懂。
注重溝通還可以幫助我們改進(jìn)思想,讓它更加現(xiàn)實。一方面是由于投入更多的思考,考
慮“如果別人看到這段代碼會怎么想”所需要調(diào)動的腦細(xì)胞:和只關(guān)注自己是不一樣的。
這時我會退后一步,從全新的視角來審視面對的問題和解決方案。另一方面則是由于壓力
的減輕,因為我知道自己所做的事情是在務(wù)正業(yè),我做的是;付的.最后,作為社會性的產(chǎn)
物,明確地考慮社會因素要比在假設(shè)它們不存在的情況下工作更為現(xiàn)實。
3.1.2簡單
在VisualDisplayofQuantitativeInformation一書中,EdwardTufte做過一個實
驗,他拿過一張圖,把上面沒有增加任何信息的標(biāo)記全都擦理,最終得到了一張很新穎的
圖,比原先那張更容易理解。
去掉多余的復(fù)雜性可以讓那些閱讀、使用和修改代碼的人更容易理解。有些復(fù)雜性是內(nèi)
在的,它們準(zhǔn)確地反映出所要解決的問題的復(fù)雜性。但有些復(fù)雜性的產(chǎn)生完全是因為我們
忙著讓程序運(yùn)行起來,在擺弄過程中留下來的“指甲印”沒擦干凈。這種多余的復(fù)雜性降
低了軟件的價值,因為一方面軟件正確運(yùn)行的可能性降低了,另一方面將來也很難進(jìn)行正
確的改動?;仡欁约鹤鲞^的事情,把麥子和糠分開,是編程中不可或缺的一部分。
簡單存在于旁觀者的眼中。一個可以將專業(yè)工具使用得得心應(yīng)手的高級程序員,他所認(rèn)
為的簡單事情對一個初學(xué)者來說可能會比登天還難。只有把讀者放在心里,你才可以寫出
動人的散文。同樣,只有把讀者放在心里,你才可以編寫出優(yōu)美的程序。給閱讀者一點挑
戰(zhàn)沒有關(guān)系,但過多的復(fù)雜性會讓你失去他們。
在復(fù)雜與簡單的波動中,計算機(jī)技術(shù)不斷向前推進(jìn)。直到微型計算機(jī)出現(xiàn)之前,大型機(jī)
架構(gòu)的發(fā)展傾向仍然是越來越復(fù)雜。微型計算機(jī)并沒有解決大型機(jī)的所有問題,只不過在
很多應(yīng)用中,那些問題已經(jīng)變得不再重要。編程語言也在復(fù)雜和簡單的起伏中前行。C++
在C的基礎(chǔ)上產(chǎn)生,而后在C++的基礎(chǔ)上又出現(xiàn)了Java,現(xiàn)在Java本身也變得越來越復(fù)
雜了。
追求簡單推動了進(jìn)化。JUnit比它所大規(guī)模取代的上一代測試工具簡單得多。JUnit催
生了各種模仿者、擴(kuò)展軟件和新的編程/測試技術(shù)。它最近一個版本JUnit4已經(jīng)失去了
那種“一目了然”的效果,雖然每一個導(dǎo)致其復(fù)雜化的決定都有我參與其中,但亦未能阻
止這種趨勢??傆幸惶欤瑫腥税l(fā)明一種比JUnil簡單許多的方式,以方便編程人員編
寫測試。這種新的想法又會推動另一輪進(jìn)化。
在各個層次上都應(yīng)當(dāng)要求簡單。對代碼進(jìn)行調(diào)整,刪除所有不提供信息的代碼。設(shè)計中
不出現(xiàn)無關(guān)元素。對需求提出質(zhì)疑,找出最本質(zhì)的概念。去淖多余的復(fù)雜性后,就好像有
一束光照亮了余卜.的代碼,你就有機(jī)會用全新的視角來處理它們。
溝通和簡單通常都是不可分割的。多余的復(fù)雜性越少,系統(tǒng)就越容易理解;在溝通方面
投入越多,就越容易發(fā)現(xiàn)應(yīng)該被拋棄的復(fù)雜性。不過有時候我也會發(fā)現(xiàn)某種簡化會使程序
難以理解,這種情況下我會優(yōu)先考慮溝通。這樣的情形很少,但常常都表示這里應(yīng)該有一
些我尚未察覺的更大規(guī)模的簡化。
3.1.3靈活
在三種價值觀中,靈活是衡量那些低效編碼與設(shè)計實踐的一把標(biāo)尺。以獲取一個常量為
例,我曾經(jīng)見到有人會用環(huán)境變量保存一個目錄名,而那個目錄下放著一個文件,文件中
寫著那個常量的值。為什么弄這么復(fù)雜?為了靈活。程序是應(yīng)該靈活,但只有在發(fā)生變化
的時候才需如此。如果這個常量永遠(yuǎn)不會變化,那么付出的代價就都白費(fèi)了。
因為程序的絕大部分開銷都是在它笫一次部署以后才產(chǎn)生,所以程序必須要容易改動。
想象中明天或許會用得上的靈活性,可能與真正修改代碼時所需要的靈活性不是一回事。
這就是簡單性和大規(guī)模測試所帶來的靈活性比專門設(shè)計出來的靈活性更為有效的原因。
要選擇那些提倡靈活性并能夠帶來及時收益的模式。對于會立刻增加成本但收效卻緩慢
的模式,最好讓自己多一點耐心,先把它們放回口袋里,需要的時候再拿出來。這樣就可
以用最恰當(dāng)?shù)姆绞绞褂盟鼈儭?/p>
靈活性的提高可能以復(fù)雜性的提高為代價。比如說,給用戶提供一個可自定義配置的選
擇提高了靈活性,但是因為多了一個配置文件,編程時也需要考慮這一點,所以也就更好
雜反過來簡單也可以促進(jìn)艮活。在前面的例了?中,如果可以找到取消配置選項但又不
喪失價值的方式,那么這個程序以后就更容易改動。
增進(jìn)軟件的溝通效果同樣會提高靈活性。能夠快速閱讀、理解和修改你的代碼的人越多,
它將來發(fā)生變化的選擇就越多C
本書中介紹的模式會通過幫助編程人員創(chuàng)建簡單、可以理解、可以修改的應(yīng)用程序來提
高程序的靈活性。
3.2原則
實現(xiàn)模式并不是無緣無故產(chǎn)生的。每一種模式都或多或少體現(xiàn)了溝通、簡單和靈活這些
價值觀。原則是另一個層次上的通用思想,比價值觀更貼近于編程實際,同時又是模式的
基礎(chǔ)。
我們有很多理由來檢查一下這些原則。正如元素周期表幫助人們發(fā)現(xiàn)了新的元素,清晰
的原則也可以引出新的模式。原則可以解釋模式背后的動機(jī),它是有普遍意義的。在對立
模式間進(jìn)行選擇時,最好的方式就是用原則來說話,而不是讓模式爭來爭去。最后,如果
遇到從未碰到過的情況,對原則的理解可以充當(dāng)我們的向?qū)А?/p>
例如,假如要使用新的編程語言,我可以根據(jù)自己對原則的理解發(fā)展出有效的編程方式,
不必盲目模仿現(xiàn)有的編程方式,更不用拘泥于在其他語言中形成的習(xí)慣(雖然可以用任何
語言編寫FORTRAN風(fēng)格的代碼,但不該那么做)。對原則的充分理解使我能夠快速地學(xué)習(xí),
即使在新鮮局面下仍然能夠一以貫之地符合原則。接下來的部分,我將為你講述隱藏在模
式背后的原則。
3.2.1局部化影響
組織代碼結(jié)構(gòu)時,要保證變化只會產(chǎn)生局部化影響。如果這里的一個變化會引出那里的
一個問題,那么變化的代價就會急劇上升了。把影響范圍縮到最小,代碼就會有極佳的溝
通效果。它可以被逐步深入理解,不必一開始就要鳥瞰全景。因為實現(xiàn)模式背后一條最主
要的動機(jī)就是減少變化所引起的代價,所以局部化影響這條原則也是很多模式的形成緣由
之一O
3.2.2最小化重復(fù)
最小化重復(fù)這條原則有助于保證局部化影響。如果相同的代碼出現(xiàn)在很多地方,那么改
動其中一處副本時,就不得不考慮是否需要修改其他副本;變動不再只發(fā)生在局部。代碼
的復(fù)制越多,變化的代價就越大。
復(fù)制代碼只是重復(fù)的一種形式。并行的類層次結(jié)構(gòu)也是其一,同樣破壞了局部化影響原
則。如果修改一處概念需要修改兩個或更多的類層次結(jié)構(gòu),就表示變化的影響已經(jīng)擴(kuò)散了。
此時應(yīng)重新組織代碼,讓變化只對局部產(chǎn)生影響。這種做法可以有效改進(jìn)代碼質(zhì)量。
重復(fù)不容易被預(yù)見到,有時在出現(xiàn)以后一段時間才會被覺察。重復(fù)不是罪過,它只是增
加了變化的開銷。
我們可以把程序拆分成許多更小的部分一一小段語句、小段方法、小型對象和小型包,
從而消除重復(fù)。大段邏輯很容易與其他大段邏輯出現(xiàn)重復(fù)的代碼片斷,于是就有了模式誕
生的可能,雖然不同的代碼段落中存在差異,但也有很多相以之處。如果能夠清晰地表述
出哪些部分程序是等同的,哪些部分相似性很少,而哪些部分則截然不同,程序就會更容
易閱讀,修改的代價也會更小C
3.2.3將邏輯與數(shù)據(jù)捆綁
局部化影響的必然結(jié)果就是將邏加與數(shù)據(jù)捆綁。把邏輯與邏輯所處理的數(shù)據(jù)放在一起,
如果有可能盡量放到一個方法中,或者退一步,放到一個對象里面,最起碼也要放到一個
包下面。在發(fā)生變化時,邏輯沏數(shù)據(jù)很可能會同時被改動。如果它們被放在一起,那么修
改它們所造成的影響就會只停留在局部。
在編碼開始的那一刻,我們往往不太清楚該把邏輯和數(shù)據(jù)放到哪里。我可能在A中編寫
代碼的時候才意識到需要B中的數(shù)據(jù)。在代碼正常工作之后,我才意識到它與數(shù)據(jù)離得太
遠(yuǎn)。這時候我需要做出選擇:是該把代碼挪到數(shù)據(jù)那邊去,還是把代碼挪到邏輯這邊來,
或者把代碼和數(shù)據(jù)都放到一個輔助對象中?也許還可能意識到,這時我還沒法找出如何組
合它們以便增進(jìn)溝通的最好方式。
3.2.4對稱性
對稱性也是我隨時隨地運(yùn)用的一項原則。程序中處處充滿了對稱性。比如add。方法總
會伴隨著remove。方法,一組方法會接受同樣的參數(shù),一個對象中所有的字段都具有相
同的生命周期。識別出對稱性,把它清晰地表述出來,代碼將更容易閱讀。一旦閱讀者理
解了對稱性所涵蓋的某一半,他們就會很快地理解另外一半。
對稱性往往用空間詞匯進(jìn)行表述:左右對稱的、旋轉(zhuǎn)的,等等。程序中的時稱性指的是
概念上的對稱,而不是圖形上的對稱。代碼中對稱性的表現(xiàn),是無論在什么地方,同樣的
概念都以同樣的形式呈現(xiàn)。
這是一個缺少對稱性的例子:
voidprocess(){
input();
count++;
output();
)
第二條語句比其他的語句更加具體。我會根據(jù)對稱性的原則重寫它,結(jié)果是:
voidprocess(){
input();
incrementCount();
output();
)
這個方法依然違反了對稱性。這里的input。和output。操作都是通過方法意圖來命
名的,但是incrementCountC這個方法卻以實現(xiàn)方式來命名。在追求對稱性的時候,我
會考慮為什么我會增加這個數(shù)值,于是就有了下面的結(jié)果:
voidprocess(){
input();
tallyO;
output();
)
在準(zhǔn)備消滅重復(fù)之前,常常需要尋找并表示出代碼中的對稱性。如果在很多代碼中都存
在類似的想法,那么可以先把它們用對稱的方式表示出來,讓接下來的重構(gòu)有一個良好開
端。
3.2.5聲明式表達(dá)
實現(xiàn)模式背后的另一條原則是盡可能聲明式地表達(dá)出意圖C命令式的編程語言功能強(qiáng)大
靈活,但是在閱讀時需要跟隨著代碼的執(zhí)行流程。我必須在大腦中建起一個程序狀態(tài)、控
制流和數(shù)據(jù)流的模型。對于那些只是陳述簡單事實,不需要一系列條件語句的程序片斷,
如果用簡單的聲明方式寫出來,讀著就容易多了。
比如在JUnit的早期版本中,測試類里可能會有一個靜態(tài)的suite。方法,該方法會返
回需要運(yùn)行的測試集合。
publicstaticjunit.framework.TestsuiteO{
Testresult=newTestSuiteO;
…complicatedstuff...
returnresult;
}
現(xiàn)在就有了一個很簡單很常見的問題:哪些測試會被執(zhí)行?在大多數(shù)情況下,suite。
方法只是將多個類中的測試匯總起來。但是因為它是一個通用方法,所以我必須要讀過、
理解該方法以后,才能夠百分之百確定它的功能。
Knit4用了聲明式表達(dá)原則來解決這個問題。它不是用一個方法來返回測試集,而是
用了一個特殊的testrunner來執(zhí)行多個類中的所有測試(這是最常見的情況):
?RunWith(Suite,class)
?TestClasses({
SimpleTcst.class,
ComplicatedTest.class
})
classAlITests{
)
如果測試是用這種方式匯總的,那么我只需要讀一下TeslClasses注解就可以知道哪此
測試會被執(zhí)行。面對這種聲明式的表達(dá)方式,我不需要臆測會出現(xiàn)什么奇怪的例外情況。
這個解決方案放棄了原始的suite。方法所具備的能力和通用性,但是它聲明式的風(fēng)格使
得代碼更加容易閱讀。(在運(yùn)行測試方面,RunWith注解比suite。方法更為靈活,但這
應(yīng)該是另外一本書里的故事了c)
3.2.6變化率
最后一個原則就是把具有相同變化率的邏輯、數(shù)據(jù)放在一遠(yuǎn),把具有不同變化率的邏輯、
數(shù)據(jù)分離。變化率具有時間上的對稱性。有時候可以將變化率原則應(yīng)用于人為的變化。例
如,如果開發(fā)一套稅務(wù)軟件,我會把計算通用稅金的代碼和計算某年特定稅金的代碼分離
開。兩類代碼的變化率是不同的。在下一年中做調(diào)整的時候:我會希望能夠確保上一年中
的代碼依然奏效。分離兩類代瑪可以讓我更確信每年的修改只會產(chǎn)生局部化影響。
變化率原則也適用于數(shù)據(jù)。一個對象中所有成員變量的變化率應(yīng)該差不多是相同的。只
會在?個方法的生命周期內(nèi)修改的成員變量應(yīng)該是局部變量。兩個同時變化但又和其他成
員的變化步調(diào)不一致的變量可能應(yīng)該屬于某個輔助對象。比如金融票據(jù)的數(shù)值與幣種會同
時變化,那么這兩個字段最好放到一個輔助對象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;
)
變化率原則也是對稱性的一個應(yīng)用,不過是時間上的對稱。在上面的例子中,value和
currency這兩個初始字段是對稱的,它們會同時變化。但它們與對象中其他的字段是不
對稱的。把它們放到自己應(yīng)該從屬的對象中,讓新的對象向閱讀者傳達(dá)出它們的對稱關(guān)系,
這樣就更有可能在將來消除重復(fù),進(jìn)一步達(dá)到影響的局部化。
3.3小結(jié)
本章介紹了實現(xiàn)模式的理論基礎(chǔ)。溝通、簡單和靈活這三條價值觀為模式提供了廣泛的
動機(jī)。局部化影響、最小化重復(fù)、將邏輯與數(shù)據(jù)捆綁、對稱性、聲明式表達(dá)和變化率這6
條原則幫助我們將價值觀轉(zhuǎn)化為實際行動。接下來我們將會進(jìn)入模式的世界,看一看針對
編程實戰(zhàn)中頻繁出現(xiàn)的問題,會有哪些特定的解決方案。
注重通過代碼與人溝通是一件有價值的事情,我們將在下一章“動機(jī)”中探尋其背后的
經(jīng)濟(jì)因素。
第4章動機(jī)
30年前,Yourdon和Constantine在StructuredDesign一書中將經(jīng)濟(jì)學(xué)作為了軟件設(shè)
計的底層驅(qū)動力。軟件設(shè)計應(yīng)該致力于減少整體成本。軟件成本cost...,可以被分解為初
始成本costlwiw和維護(hù)成本cost-!.....:
COSt.0..!=COStdi+costa…
當(dāng)這個行業(yè)在軟件開發(fā)的過程中慢慢積累了經(jīng)驗以后,人們發(fā)現(xiàn),軟件的維護(hù)成本要遠(yuǎn)
遠(yuǎn)高于它的初始成本。這個結(jié)果讓大多數(shù)人都倒吸了一口冷氣。(那些對維護(hù)的需求很小
或者根本不需要維護(hù)的項目,它們所使用的模式應(yīng)該和本書所講述的實現(xiàn)模式迥異。)
維護(hù)的代價很大,這是因為理解現(xiàn)有代碼需要花費(fèi)時間,而且容易出錯。知道r需要修
改什么以后,做出改動就變得輕而易舉了。掌握現(xiàn)在的代碼做了哪些事情是最需要花費(fèi)人
力物力的部分。改動之后,還要進(jìn)行測試和部署。
costa…=COStw-+COStz+COStt?t+cost-
減少整體成本的策略之一是在初期的開發(fā)中投入更多精力,希望借此減少甚至消除維護(hù)
的需要。這些做法往往會失敗C一旦代碼以未預(yù)期的方式發(fā)生變化,人們所曾做出的任何
預(yù)見都不再是萬全之策。人們可能會為了防備將來發(fā)生的變叱而過早考慮代碼的通用性,
但如果出現(xiàn)了沒有預(yù)料到而又勢在必行的變化,先前的做法往往就會與現(xiàn)實發(fā)生沖突。
從本質(zhì)上看,增加軟件的先期投入是與兩條重要的經(jīng)濟(jì)學(xué)原則一一金錢的時間價值和未
來的不確定性一一相悖的。今天的一元錢會比明天的一元錢更值錢,所以從原則上講,我
們的實現(xiàn)策略應(yīng)該是盡量將支出推后。同樣,由于不確定性的存在,實現(xiàn)策略應(yīng)該更傾向
十帶來即時收益血非長遠(yuǎn)收益c這聽上去好像在鼓勵人們目光短淺?些,不去考慮將來,
但實際上這幽實現(xiàn)模式一方面著眼于獲得即時收益,另一方面也在創(chuàng)建干凈的代碼,以方
便將來的開發(fā)工作。
我用來減少整體成本的策略是,要求所有開發(fā)人員在進(jìn)行維護(hù)的時候注重程序員與程序
員之間的溝通,減少理解代碼所帶來的代價。清晰明確的代碼會帶來即時收益:代碼缺陷
更少,更易共享,開發(fā)曲線更加平滑。
將一些實現(xiàn)模式形成習(xí)慣后,我的開發(fā)速度得到了提升,令我分心的想法也更少了。剛
開始寫下最初的幾個實現(xiàn)模式的時候(TheSmalltalkBestPracticePatterns,
PrenticeHall,1996),我自以為是個編程能手。為了促使自己把注意力放在模式上,
我在記錄下所遵守的模式之前一個字符的代碼也不肯輸入。那段經(jīng)歷實在是很折磨人,就
像把手指扭結(jié)在一起打字一樣c在第一個星期內(nèi),每編寫一分鐘的程序都要先進(jìn)行幾個小
時的寫作。到了第二個星期,我發(fā)現(xiàn)大多數(shù)的基本模式都已經(jīng)就緒了,大部分時間我只是
在遵守這些現(xiàn)成的模式編程而己。還沒到第三個星期,我就比從前的開發(fā)速度快了很多,
因為我已經(jīng)認(rèn)認(rèn)真真地檢查過自己的開發(fā)方式,不會再有各種疑惑在我大腦中反復(fù)嘮叨干
擾思路了。
這些實現(xiàn)模式只有一部分是我自己的發(fā)明。我的開發(fā)方式芍很大一部分都是從早一代程
序員那里借鑒過來的。這些良好的編程習(xí)慣存在于那些容易閱讀、容易理解并容易維護(hù)的
代碼之中,將它們落為明文以后,我的編碼速度得到了提升,也變得更加流暢。在為將來
做好準(zhǔn)備的同時,我還可以更快地完成今天的代碼。
在編寫本書的過程中,我既總結(jié)了個人的編程習(xí)慣,也從已有的代碼中尋找靈感。我讀
過JDK、Eclipse和我以往開發(fā)經(jīng)歷中的代碼,并將它們進(jìn)行了比較。最后所形成的這些
模式,是想幫助讀者清晰地認(rèn)識到該如何編寫人們可以理解的代碼。關(guān)注的方向不同,價
值觀念不同,就會形成不同的模式。比如在“改進(jìn)框架”一空中,我撰寫了專門適合開發(fā)
框架的實現(xiàn)模式。開發(fā)框架時的價值取向不同于一般開發(fā),所以其實現(xiàn)模式也不同于一般
的實現(xiàn)模式。
就像為經(jīng)濟(jì)目的服務(wù)一樣,實現(xiàn)模式也在為人類服務(wù)。代瑪來自于人,服務(wù)于人。編程
人員可以使用實現(xiàn)模式來滿足人本身的需要,比如從工作中獲得成就感,或者成為社區(qū)中
為人信任的一員。在后續(xù)的章節(jié)中,我會繼續(xù)討論模式給人和經(jīng)濟(jì)兩方面帶來的影響。
第5章類
類的概念早在柏拉圖之前就出現(xiàn)了。比如說,5種柏拉圖立體[1]就是5個類,它
們的實例隨處可見。柏拉圖立體是絕對完美的,但它們并不實際存在。至于我們身邊那些
觸手可及的實例,它們總有某些不甚完美的方面。
面向?qū)ο缶幊滔癜乩瓐D之后的西方哲學(xué)家一樣延續(xù)了這種思維。面向?qū)ο缶幊贪殉绦騽?/p>
分成許多類,類是對一組相似的東西的一般歸納,而對象則是這些東西本身。
類對于溝通很重要,因為它們可以描述很多具體的東西。實現(xiàn)模式最大的跨度只到類一
級;與之相比,設(shè)計模式則主要是在討論類與類之間的關(guān)系。
本章將會介紹下列模式:
類(Class)一—用一個類來表示“這些數(shù)據(jù)應(yīng)該放在一起,還有這些邏輯應(yīng)該也和它
們在一起”;
簡單的超類名(SimpleSuperclassName)---位于繼承體系根上的類應(yīng)該有簡單的名
字,用以描繪它的隱喻;
限定性的子類名(QualifiedSubclassName)----子類的名字應(yīng)該表達(dá)出它與超類之
間的相似性和差異性;
抽象接口(AbstractInterface)---將接口與實現(xiàn)分離;
interface---用Java的interface機(jī)制來表現(xiàn)不常變化的抽象接口;
有版本的interface(VersionedInterface)---引入新的子interface,從而安全地
對interface進(jìn)行擴(kuò)展;
抽象類(AbstractClass)——用抽象類來表現(xiàn)很可能變化的抽象接口;
值對象(ValueObject)一一這種對象的行為就好像數(shù)值一樣;
特化(Specialization)——清晰地描述相關(guān)計算之間的相似性和差異性;
子類(Subclass)——用一人子類表現(xiàn)一個維度上的變化;
實現(xiàn)器(Implementor)一一覆蓋一個方法,從而表現(xiàn)一種計算上的變化;
內(nèi)部類(InnerClass)——將只在局部有用的代碼放在一個私有的類中;
實例特有的行為(Instance-specificBehavior)---每個實例的邏輯都有不同;
條件(Conditional)---明確指定條件,以表現(xiàn)不同的邏輯;
委派(Delegation)——把操作委派給不同類型的對象,以表現(xiàn)不同的邏輯;
可插拔的選擇器(PluggableSelector)一一通過反射來調(diào)用方法,以表現(xiàn)不同的邏輯;
匿名內(nèi)部類(AnonymousInnerClass)---在方法內(nèi)部創(chuàng)建一個新對象,并覆蓋其中
的一兩種方法,以表現(xiàn)不同的邏輯:
庫類(LibraryClass)一一如果一組功能不適合放進(jìn)任何刈象,就將其描述為一組靜
態(tài)方法。
5.1類
數(shù)據(jù)的變化比邏輯要頻繁得多,正是這種現(xiàn)象讓類有了存在的意義。每個類其實就是這
樣一個聲明:這些邏輯應(yīng)該放在一起,它們的變化不像它們所操作的數(shù)據(jù)那么頻繁;這些
數(shù)據(jù)也應(yīng)該放在一起,它們變化的頻率差不多,并且由與之關(guān)聯(lián)的邏輯來負(fù)責(zé)處理。這種
“數(shù)據(jù)會變、邏輯不變”的劃分并非絕對適用:有時隨著數(shù)據(jù)值的不同,邏輯也會有所不
同;有時邏輯也會發(fā)生相當(dāng)大的變化;有時數(shù)據(jù)本身在計算的過程中反倒不會改變。學(xué)會
如何用類來包裝邏輯和如何表達(dá)邏輯的變化,這是有效使用對象編程的重要部分。
把多個類放進(jìn)一個繼承體系可以縮減代碼量,比原封不動地把超類的內(nèi)容照抄到所有子
類精簡得多。和所有縮減代碼量的技巧一樣,它也讓代碼變得更難讀懂;必須理解超類的
上下文,然后才有可能理解子類。
正確使用繼承也是有效使用對象編程的一方面。子類傳遞的信息應(yīng)該是:我和超類很像,
只有少許差異。(我們經(jīng)常說在“子類”中“覆蓋”一種方法,這聽起來難道不奇怪嗎?
要是當(dāng)初精心挑選一個好的隱喻,程序員的日子應(yīng)該好過得多吧。)
在由對象搭建而成的程序中,類是相對昂貴的設(shè)計元素。一個類應(yīng)該做一些有直接而明
顯的意義的事情。減少類的數(shù)量是對系統(tǒng)的改進(jìn),只要剩下的類不因此而變得臃腫就好。
后面的模式介紹了如何通過類的聲明來表達(dá)設(shè)計思路。
5.2簡單的超類名
找到一個貼切的名字是編程中最令人開心的時刻之一。你一直為一個含糊不清的念頭而
困擾,代碼變得越來越復(fù)雜,但你總覺得它可以不必那么復(fù)雜。然后,往往是在閑聊時,
有人冒了一句:“噢,我明白了,不就是個調(diào)度器(Scheduler)嗎?!庇谑谴蠹蚁蚝笠?/p>
靠,長舒一口氣。貼切的名字能引發(fā)連鎖反應(yīng),帶來更深入的簡化與改進(jìn)。
在所有的命名當(dāng)中,類的命名是最重要的。類是維系其他概念的核心。一旦類有了名字,
其中操作的名字也就順理成章了。相反的情況卻很少成立,除非類的名字一開始命名得太
糟糕。
條名的“簡短”與“表現(xiàn)力”之間存在張力。你會在交談中用到類名:“記得在平移
Figure之前先要旋轉(zhuǎn)一下嗎?所以類名應(yīng)該簡明扼要,但有時候一個類名又要用到好幾
個單詞才足夠精確。
擺脫這種兩難境地的辦法就是給計算邏輯找到強(qiáng)有力的隱喻。腦子里有了隱,喻,一個個
單詞就不只是單詞,而是一張張關(guān)系、連接和暗示的大網(wǎng)。比如說在開發(fā)HotDraw這個繪
圖框架時,我一開始把圖畫(drawing)中的對象命名為DrawingObjectoWard
Cunningham帶來了一個印刷方面的隱喻:一幅圖畫就好像卬刷出來、排版妥當(dāng)?shù)募堩摚?/p>
紙頁上畫出來的元素正是圖形(figure),于是這個類的名字就變成了Figureo有了這
個隱喻作為鋪墊,F(xiàn)igure這個名字不僅比原來的DrawingObject更簡短,而且更準(zhǔn)確、
更具表現(xiàn)力。
有時候需要花些時間,能想出一個好名字。甚至可能代碼已經(jīng)“完工”,投入運(yùn)行了好
幾周、好幾個月甚至(我真的遇到過這種情況)好幾年之后,突然想到了一個更好的類名。
有時候需要強(qiáng)迫自己找到一個好名字,抽出一本辭典,寫下所有多少有些接近的名字,站
起來走一走。另一些時候應(yīng)該帶著挫敗感和對時間的信心先去考慮新功能的實現(xiàn),潛意識
會默默起作用的。
交談總能幫助我想出更好的名字。要嘗試把一個對象的用途解釋給別人聽,我就得尋找
具有表現(xiàn)力和感染力的圖景來描述它,這樣的圖景往往能引出新的名字。
對于重要的類,盡量用一個單詞來為它命名。
5.3限定性的子類名
子類的名字有兩重職責(zé),不僅要描述這些類像什么,還要說明它們之間的區(qū)別是什么。
同樣,在這里需要權(quán)衡長度與表現(xiàn)力。與位于繼承體系根上的超類不同,子類的名字在交
談中用得并不頻繁,所以值得以犧牲簡明來換取更好的表現(xiàn)力。通常在超類名的基礎(chǔ)上擴(kuò)
展一兩個詞就可以得到子類名C
這條規(guī)則也有例外:如果繼承只是用作共享實現(xiàn)的機(jī)制,并且子類本身就代表一個重要
的概念,那么這樣的子類就應(yīng)該被視為它自己的繼承體系的艱,擁有一個簡單的名字。舉
例來說,HotDraw里有一個Handle類,代表當(dāng)圖形被選中時對其進(jìn)行編輯操作。盡管它
繼承自Figure類,它還是有一個簡單的名字:Handle。在它之下還有一大堆的子類,它
們的名字也大多擴(kuò)展自Handle,例如StretchyHandle>TransparencyHand1e等。由于
Handle是這個繼承體系的根,因此它更應(yīng)該取一個簡單的超類名,而不是加上各種修飾
語擴(kuò)展而成的子類名。
給多級繼承體系中的子類命名也是一個難題。一般而言,多級繼承體系應(yīng)該進(jìn)行重構(gòu),
換成使用委派,但既然它們還在這里,就應(yīng)該給它們?個好名字。不要不假思索地在直接
超類的基礎(chǔ)上擴(kuò)展出子類名,要多從閱讀者的角度來想想閱讀者需要了解這個類的什么信
息。你應(yīng)該帶著這個思考,以超類名為參考來給子類命名。
與他人溝通是類名的用途,如果僅僅為了和計算機(jī)溝通,只要給每個類編號就足夠了。
太長的類名讀寫都費(fèi)勁,太短的類名又會考驗閱讀者的記憶力。如果一組類的名字體現(xiàn)不
出它們之間的相關(guān)性,閱讀者就很難對?它們形成整體印象,也很難回憶起它們的關(guān)系。應(yīng)
該用類名來講述代碼的故事。
5.4抽象接口
請牢記軟件開發(fā)的占訓(xùn):針對接口編程,不要針對實現(xiàn)編程。從另一個角度來說,這也
意味著設(shè)計決策不應(yīng)該暴露給不必要的地方。如果大部分代碼只知道我在處理一個容器,
那么我就可以隨時改變這個容器的具體實現(xiàn)。但有時不得不指定具體類,否則計算就沒法
進(jìn)行下去。
這里所說的“接口”是指“一組沒有實現(xiàn)的操作”。在Java中,接口這個概念既可以
表現(xiàn)為interface,也可以表現(xiàn)為超類。隨后的兩個模式會分別指出兩者的適用場景。
每層接口都有成本:需要學(xué)習(xí)它,理解它,給它寫文檔,調(diào)試它,組織它,瀏覽它,還
有給它命名。并不是接口數(shù)量越多軟件成本就會越少,只有需要接口帶來的靈活性時才值
得為它付出成本。所以,既然很多時候并不能提前知道是否需要接口帶來的靈活性,出于
降低成本的考慮,在仔細(xì)考慮“哪些地方需要接口”的同時,最好是在真正需要這種靈活
性時再引入接I」。
盡管我們成天都在抱怨軟件不夠靈活,但很多時候系統(tǒng)根本不需要變得更靈活。不管是
要進(jìn)行基礎(chǔ)性的修改(例如改變整數(shù)類型的字節(jié)數(shù))還是大范圍的修改(例如引入新的商
業(yè)模型),大部分軟件都不會需要最大限度的那種靈活性。
在引入接口時的另一個經(jīng)濟(jì)方面的考量是軟件的不可.預(yù)測性。我們這個行業(yè)似乎已經(jīng)沉
溺于這樣一種觀念:只要一開始設(shè)計正確,軟件系統(tǒng)就不需要任何變動。最近我讀到了一
份關(guān)于“軟件變更的理由”的列表,其中列舉的條目包括程序員沒有弄清需求、客戶改變
了想法,等等,唯一沒有提到的是正當(dāng)?shù)淖兏?。這樣的一份列表傳達(dá)出的信息是:變更總
是錯誤的??墒?,為什么一份天氣預(yù)報不能永遠(yuǎn)正確呢?因為天氣以不可預(yù)測的方式變化。
同樣的道理,為什么我們不能一次列出系統(tǒng)中所有需要靈活性的地方呢?因為需求和技術(shù)
都在以不可預(yù)測的方式變化。這并非要給我們程序員免責(zé),我們?nèi)匀灰M全力開發(fā)客戶當(dāng)
下需要的系統(tǒng);但它讓我們知道,通過預(yù)先思考來弄清軟件格來的樣子,其效果是相當(dāng)有
限的。
所有這些因素一一對靈活性的需要、靈活性的成本、”何處需要靈活性”的不可預(yù)測-
-加在一起讓我相信:應(yīng)該在詢定無疑地需要靈活性時,才應(yīng)該引入這種靈活性。引入靈
活性是有代價的,因為需要修改已有的軟件。如果不能獨(dú)自完成所有需要的修改,成本就
會更高,我們在后面關(guān)于“改進(jìn)框架”的章節(jié)中會詳細(xì)討論這個話題。
Java有兩種方式來表現(xiàn)抽象接口:超類和interface。它們在應(yīng)對變化時涉及的成本
各有不同。
5.5interface
要用Java表達(dá)“這是我要完成的任務(wù),除此之外的細(xì)節(jié)不歸我操心",可以聲明一個
interface。interface是Java率先引入編程語言市場主流的重要創(chuàng)新之一。interface
是一個很好的平衡,它帶來了多繼承的一部分靈活性,同時又沒有多繼承的復(fù)雜性和二義
性。一個類可以實現(xiàn)多個interface。interface只有操作,沒有成員變量,所以它們能
夠有效保護(hù)其使用者不受實現(xiàn)變化的侵?jǐn)_。
如果說interface讓改變的工作更加輕松,那么不能不提的就是對接口本身的修改是不
被鼓勵的;一旦在interface上增加或者修改方法,就必須同時改變所有的實現(xiàn)類。如果
無權(quán)改變實現(xiàn),大量使用interface會嚴(yán)重拖累日后的設(shè)計調(diào)整。
此外interface的一個特點也影響了它們作為溝通手段的價值:其中所有的操作都必須
是public的。我經(jīng)常會希望在interface中聲明一些包內(nèi)可見的操作。如果程序只是小
范圍使用,讓設(shè)計元素的可見性略微高一點還不算什么大問題。但加果要把接口發(fā)布給很
多人去用,那么最好是準(zhǔn)確地指定希望讓他們看到哪些操作。不要因為一時懶惰斷了自己
的后路。
給interface命名有兩種風(fēng)格,選擇哪一種取決于如何看待它們。如果把interface看
作“沒有實現(xiàn)的類”,那么就應(yīng)該像給類命名一樣地給它們命名(簡單的超類名、限定性
的子類名)。這種命名風(fēng)格的問題是:接口會占掉那個最貼切的名字,給類命名的時候就
不能用了。舉例來說,如果一個interface叫File,那么它的實現(xiàn)類就只好叫
ActualFile>ConcreteFile或者(可惡?。〦ilelmpl(后綴加縮寫)之類的。一般情況
下,有必要讓使用者知道自己究竟是在操作具體的對象還是油象的接口,至于這個“抽象
的接口”究竟是interface還是超類倒是不那么要緊。用這種命名規(guī)則,可以不必一上來
就把interface和超類劃清界F艮,于是你就可以在有必要時改變想法。
但有時候比起隱蔽“此處使用interface”這一事實來,具體類的命名刈于交流更加重
要。在這種情況下,可以給interface的名字加上“I”前綴:如果interface的名字是
IFile,那么實現(xiàn)類就可以叫File了。
5.6抽象類
在Java中區(qū)分抽象接口與具體實現(xiàn)的另一種方式是使用超類。超類是抽象的,因為超
類的引用可以在運(yùn)行時替換為任何子類的對象;至于這個超類在Java的語法意義上是不
是抽象的,這并不重要。
何時應(yīng)該使用超類,何時應(yīng)該使用interface?取舍最終歸結(jié)為兩點:接口會如何變化,
實現(xiàn)類是否需要同時支持多個接口。抽象接口需要支持實現(xiàn)的變化以及接口本身的變化兩
種類型的變化。Java的interface對后者的支持不佳;一旦改變interface,所有的實
現(xiàn)類都必須同時修改。如果要修改一個有很多實現(xiàn)類的interface,很容易導(dǎo)致現(xiàn)有的設(shè)
計陷入癱瘓,以致只好借助有版本的interface來調(diào)整設(shè)計。
抽象類則沒有這方面的限制。只要提供了默認(rèn)實現(xiàn),在抽象類中新增的操作就不會侵?jǐn)_
現(xiàn)有的實現(xiàn)類。
抽象類的局限體現(xiàn)在實現(xiàn)類必須對其忠心不貳。如果需要以另一種視角來看待同一個實
現(xiàn)類,就只能讓它實現(xiàn)interface了。
用abstract關(guān)鍵字來修飾一個類可以告訴閱讀者:如果要使用這個類,就必須做一些
實現(xiàn)工作。不過,只要有可能讓繼承體系根上的類被獨(dú)立創(chuàng)建和使用,就應(yīng)該這樣做。
旦走上抽象化這條路,就容易滑得太遠(yuǎn)而創(chuàng)造出沒有價值的油象。努力讓繼承體系的根也
能獨(dú)立創(chuàng)建,可以促使你消除那些不必要的抽象。
interface和類繼承體系并不是互斥的。你可以提供一個接口說“你可以使用這些功
能”,再提供一個超類說“這是一種實現(xiàn)方式”。此時使用者應(yīng)該引用接口類型,這樣未
來的維護(hù)者就可以根據(jù)需要隨時替換新的實現(xiàn)。
5.7有版本的interface
如果想要修改一個interface但又不能修改,怎么辦?這種情況通常在想要增加操作
時發(fā)生,在interface中增加操作會破壞所有現(xiàn)有的實現(xiàn)類,所以不能這樣做。不過可
以聲明一個新的interface,使它繼承原來的interface,然后在其中增加操作。如果使
用者需要新增的功能,就使用這個新的interface,其他使用者則繼續(xù)無視新interface
的存在。使用這種做法,在需要新功能時必須明確檢查對象的類型,并將其向下轉(zhuǎn)型成新
interface的類型。
比如說,考慮一個簡單的“命令”interface:
interfaceCommand{
voidrun();
}
當(dāng)這個interface被發(fā)布出去并被實現(xiàn)了成千上萬次以后,修改它的成本就會極其高昂。
但為了支持命令的取消,需要新增一個操作。用有版本的interface來解決這個問題,我
們就得到了如下的子interface:
interfaceReversib1eCommandextendsCommand{
voidundo();
)
所有現(xiàn)存的Command實例仍然照常工作,RoversiblcConmand的實例也完全可以勝任
Command的職責(zé)。如果需要使用新的操作,就需要向下轉(zhuǎn)型:
Commandrecent=…;
if(recentinstanceofReversib1eCommand){
Reversib1eCommanddowncasted=(ReversibleCommand)recent;
downcasted.undo();
)
一般情況下,使用instanceof會降低靈活性,因為這會把代碼與具體類綁定在一起。
但在這個例子里使用inslanceol應(yīng)該是合理的,因為這樣才能對interlace作出調(diào)整。
不過,如果可選的interface有很多,使用者就需要花很多精力來處理各種變化,這就表
示你需要重新思考你的設(shè)計了c
這是一種丑陋的解決方案,用來解決一
溫馨提示
- 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
- 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
- 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內(nèi)容里面會有圖紙預(yù)覽,若沒有圖紙預(yù)覽就沒有圖紙。
- 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
- 5. 人人文庫網(wǎng)僅提供信息存儲空間,僅對用戶上傳內(nèi)容的表現(xiàn)方式做保護(hù)處理,對用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對任何下載內(nèi)容負(fù)責(zé)。
- 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請與我們聯(lián)系,我們立即糾正。
- 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時也不承擔(dān)用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。
最新文檔
- 2024政府采購智慧社區(qū)管理系統(tǒng)合同3篇
- 2024簽合同附加協(xié)議書:醫(yī)療健康大數(shù)據(jù)應(yīng)用合作協(xié)議3篇
- 2025年安徽省建筑安全員考試題庫及答案
- 二零二五年度二手車交易合同標(biāo)準(zhǔn)版3篇
- 生物制造產(chǎn)業(yè)園項目可行性研究報告
- 2025年福建建筑安全員知識題庫
- 2024年環(huán)保型塑料原料采購銷售合作協(xié)議3篇
- 2025海南省安全員考試題庫附答案
- 2025版教育培訓(xùn)機(jī)構(gòu)競業(yè)禁止補(bǔ)償金核算與支付協(xié)議3篇
- 2025天津市安全員考試題庫
- 環(huán)境清潔消毒與醫(yī)院感染控制ppt課件
- 2019年血站績效考核標(biāo)準(zhǔn)
- 盤扣架支架計算小程序EXCEL
- 腦卒中康復(fù)治療流程圖
- 標(biāo)準(zhǔn)—上海市工程建設(shè)規(guī)范普通中小學(xué)校建設(shè)標(biāo)準(zhǔn)
- 《Something Just Like This》歌詞
- 人民網(wǎng)刪除稿件(帖文)申請登記表
- 橋梁加固、拼寬流程圖(共9頁)
- 小組合作學(xué)習(xí)學(xué)生評價量表
- 新錄用公務(wù)員服務(wù)協(xié)議書
- OQC崗位職責(zé)(完整版)
評論
0/150
提交評論