?? 0122.htm
字號(hào):
添加新類型需要作出的改動(dòng)是完全孤立的:我們隨同addToBin()方法繼承Trash的新類型,然后繼承一個(gè)新的TypeBin(這實(shí)際只是一個(gè)副本,可以簡(jiǎn)單地編輯),最后將一種新類型加入TrashBinSet的集合初化化過程。<br>
<br>
16.7 訪問器范式<br>
接下來,讓我們思考如何將具有完全不同目標(biāo)的一個(gè)設(shè)計(jì)范式應(yīng)用到垃圾歸類系統(tǒng)。<br>
對(duì)這個(gè)范式,我們不再關(guān)心在系統(tǒng)中加入新型Trash時(shí)的優(yōu)化。事實(shí)上,這個(gè)范式使新型Trash的添加顯得更加復(fù)雜。假定我們有一個(gè)基本類結(jié)構(gòu),它是固定不變的;它或許來自另一個(gè)開發(fā)者或公司,我們無權(quán)對(duì)那個(gè)結(jié)構(gòu)進(jìn)行任何修改。然而,我們又希望在那個(gè)結(jié)構(gòu)里加入新的多形性方法。這意味著我們一般必須在基礎(chǔ)類的接口里添加某些東西。因此,我們目前面臨的困境是一方面需要向基礎(chǔ)類添加方法,另一方面又不能改動(dòng)基礎(chǔ)類。怎樣解決這個(gè)問題呢?<br>
“訪問器”(Visitor)范式使我們能擴(kuò)展基本類型的接口,方法是創(chuàng)建類型為Visitor的一個(gè)獨(dú)立的類結(jié)構(gòu),對(duì)以后需對(duì)基本類型采取的操作進(jìn)行虛擬。基本類型的任務(wù)就是簡(jiǎn)單地“接收”訪問器,然后調(diào)用訪問器的動(dòng)態(tài)綁定方法??雌饋砭拖笙旅孢@樣:<br>
<br>
945頁(yè)圖<br>
<br>
現(xiàn)在,假如v是一個(gè)指向Aluminum(鋁制品)的Visitable句柄,那么下述代碼:<br>
PriceVisitor pv = new PriceVisitor();<br>
v.accept(pv);<br>
會(huì)造成兩個(gè)多形性方法調(diào)用:第一個(gè)會(huì)選擇accept()的Aluminum版本;第二個(gè)則在accept()里——用基礎(chǔ)類Visitor句柄v動(dòng)態(tài)調(diào)用visit()的特定版本時(shí)。<br>
這種配置意味著可采取Visitor的新子類的形式將新的功能添加到系統(tǒng)里,沒必要接觸Trash結(jié)構(gòu)。這就是“訪問器”范式最主要的優(yōu)點(diǎn):可為一個(gè)類結(jié)構(gòu)添加新的多形性功能,同時(shí)不必改動(dòng)結(jié)構(gòu)——只要安裝好了accept()方法。注意這個(gè)優(yōu)點(diǎn)在這兒是有用的,但并不一定是我們?cè)谌魏吻闆r下的首選方案。所以在最開始的時(shí)候,就要判斷這到底是不是自己需要的方案。<br>
現(xiàn)在注意一件沒有做成的事情:訪問器方案防止了從主控Trash序列向單獨(dú)類型序列的歸類。所以我們可將所有東西都留在單主控序列中,只需用適當(dāng)?shù)脑L問器通過那個(gè)序列傳遞,即可達(dá)到希望的目標(biāo)。盡管這似乎并非訪問器范式的本意,但確實(shí)讓我們達(dá)到了很希望達(dá)到的一個(gè)目標(biāo)(避免使用RTTI)。<br>
訪問器范式中的雙生派遣負(fù)責(zé)同時(shí)判斷Trash以及Visitor的類型。在下面的例子中,大家可看到Visitor的兩種實(shí)現(xiàn)方式:PriceVisitor用于判斷總計(jì)及價(jià)格,而WeightVisitor用于跟蹤重量。<br>
可以看到,所有這些都是用回收程序一個(gè)新的、改進(jìn)過的版本實(shí)現(xiàn)的。而且和DoubleDispatch.java一樣,Trash類被保持孤立,并創(chuàng)建一個(gè)新接口來添加accept()方法:<br>
<br>
946頁(yè)上程序<br>
<br>
Aluminum,Paper,Glass以及Cardboard的子類型實(shí)現(xiàn)了accept()方法:<br>
<br>
946-947頁(yè)程序<br>
<br>
由于Visitor基礎(chǔ)類沒有什么需要實(shí)在的東西,可將其創(chuàng)建成一個(gè)接口:<br>
<br>
947-948頁(yè)程序<br>
<br>
程序剩余的部分將創(chuàng)建特定的Visitor類型,并通過一個(gè)Trash對(duì)象列表發(fā)送它們:<br>
<br>
948-951頁(yè)程序<br>
<br>
注意main()的形狀已再次發(fā)生了變化?,F(xiàn)在只有一個(gè)垃圾(Trash)筒。兩個(gè)Visitor對(duì)象被接收到序列中的每個(gè)元素內(nèi),它們會(huì)完成自己份內(nèi)的工作。Visitor跟蹤它們自己的內(nèi)部數(shù)據(jù),計(jì)算出總重和價(jià)格。<br>
最好,將東西從序列中取出的時(shí)候,除了不可避免地向Trash造型以外,再?zèng)]有運(yùn)行期的類型驗(yàn)證。若在Java里實(shí)現(xiàn)了參數(shù)化類型,甚至那個(gè)造型操作也可以避免。<br>
對(duì)比之前介紹過的雙重派遣方案,區(qū)分這兩種方案的一個(gè)辦法是:在雙重派遣方案中,每個(gè)子類創(chuàng)建時(shí)只會(huì)過載其中的一個(gè)過載方法,即add()。而在這里,每個(gè)過載的visit()方法都必須在Visitor的每個(gè)子類中進(jìn)行過載。<br>
<br>
1. 更多的結(jié)合?<br>
這里還有其他許多代碼,Trash結(jié)構(gòu)和Visitor結(jié)構(gòu)之間存在著明顯的“結(jié)合”(Coupling)關(guān)系。然而,在它們所代表的類集內(nèi)部,也存在著高度的凝聚力:都只做一件事情(Trash描述垃圾或廢品,而Visitor描述對(duì)垃圾采取什么行動(dòng))。作為一套優(yōu)秀的設(shè)計(jì)方案,這無疑是個(gè)良好的開端。當(dāng)然就目前的情況來說,只有在我們添加新的Visitor類型時(shí)才能體會(huì)到它的好處。但在添加新類型的Trash時(shí),它卻顯得有些礙手礙腳。<br>
類與類之間低度的結(jié)合與類內(nèi)高度的凝聚無疑是一個(gè)重要的設(shè)計(jì)目標(biāo)。但只要稍不留神,就可能妨礙我們得到一個(gè)本該更出色的設(shè)計(jì)。從表面看,有些類不可避免地相互間存在著一些“親密”關(guān)系。這種關(guān)系通常是成對(duì)發(fā)生的,可以叫作“對(duì)聯(lián)”(Couplet)——比如集合和繼承器(Enumeration)。前面的Trash-Visitor對(duì)似乎也是這樣的一種“對(duì)聯(lián)”。<br>
<br>
16.8 RTTI真的有害嗎<br>
本章的各種設(shè)計(jì)方案都在努力避免使用RTTI,這或許會(huì)給大家留下“RTTI有害”的印象(還記得可憐的goto嗎,由于給人印象不佳,根本就沒有放到Java里來)。但實(shí)際情況并非絕對(duì)如此。正確地說,應(yīng)該是RTTI使用不當(dāng)才“有害”。我們之所以想避免RTTI的使用,是由于它的錯(cuò)誤運(yùn)用會(huì)造成擴(kuò)展性受到損害。而我們事前提出的目標(biāo)就是能向系統(tǒng)自由加入新類型,同時(shí)保證對(duì)周圍的代碼造成盡可能小的影響。由于RTTI常被濫用(讓它查找系統(tǒng)中的每一種類型),會(huì)造成代碼的擴(kuò)展能力大打折扣——添加一種新類型時(shí),必須找出使用了RTTI的所有代碼。即使僅遺漏了其中的一個(gè),也不能從編譯器那里得到任何幫助。<br>
然而,RTTI本身并不會(huì)自動(dòng)產(chǎn)生非擴(kuò)展性的代碼。讓我們?cè)賮砜匆豢辞懊嫣岬降睦厥绽印_@一次準(zhǔn)備引入一種新工具,我把它叫作TypeMap。其中包含了一個(gè)Hashtable(散列表),其中容納了多個(gè)Vector,但接口非常簡(jiǎn)單:可以添加(add())一個(gè)新對(duì)象,可以獲得(get())一個(gè)Vector,其中包含了屬于某種特定類型的所有對(duì)象。對(duì)于這個(gè)包含的散列表,它的關(guān)鍵在于對(duì)應(yīng)的Vector里的類型。這種設(shè)計(jì)方案的優(yōu)點(diǎn)(根據(jù)Larry
O'Brien的建議)是在遇到一種新類型的時(shí)候,TypeMap會(huì)動(dòng)態(tài)加入一種新類型。所以不管什么時(shí)候,只要將一種新類型加入系統(tǒng)(即使在運(yùn)行期間添加),它也會(huì)正確無誤地得以接受。<br>
我們的例子同樣建立在c16.Trash這個(gè)“包”(Package)內(nèi)的Trash類型結(jié)構(gòu)的基礎(chǔ)上(而且那兒使用的Trash.dat文件可以照搬到這里來)。<br>
<br>
952-953頁(yè)程序<br>
<br>
盡管功能很強(qiáng),但對(duì)TypeMap的定義是非常簡(jiǎn)單的。它只是包含了一個(gè)散列表,同時(shí)add()負(fù)擔(dān)了大部分的工作。添加一個(gè)新類型時(shí),那種類型的Class對(duì)象的句柄會(huì)被提取出來。隨后,利用這個(gè)句柄判斷容納了那類對(duì)象的一個(gè)Vector是否已存在于散列表中。如答案是肯定的,就提取出那個(gè)Vector,并將對(duì)象加入其中;反之,就將Class對(duì)象及新Vector作為一個(gè)“鍵-值”對(duì)加入。<br>
利用keys(),可以得到對(duì)所有Class對(duì)象的一個(gè)“枚舉”(Enumeration),而且可用get(),可通過Class對(duì)象獲取對(duì)應(yīng)的Vector。<br>
filler()方法非常有趣,因?yàn)樗昧薖arseTrash.fillBin()的設(shè)計(jì)——不僅能嘗試填充一個(gè)Vector,也能用它的addTrash()方法試著填充實(shí)現(xiàn)了Fillable(可填充)接口的任何東西。filter()需要做的全部事情就是將一個(gè)句柄返回給實(shí)現(xiàn)了Fillable的一個(gè)接口,然后將這個(gè)句柄作為參數(shù)傳遞給fillBin(),就象下面這樣:<br>
ParseTrash.fillBin("Trash.dat", bin.filler());<br>
為產(chǎn)生這個(gè)句柄,我們采用了一個(gè)“匿名內(nèi)部類”(已在第7章講述)。由于根本不需要用一個(gè)已命名的類來實(shí)現(xiàn)Fillable,只需要屬于那個(gè)類的一個(gè)對(duì)象的句柄即可,所以這里使用匿名內(nèi)部類是非常恰當(dāng)?shù)摹?lt;br>
對(duì)這個(gè)設(shè)計(jì),要注意的一個(gè)地方是盡管沒有設(shè)計(jì)成對(duì)歸類加以控制,但在fillBin()每次進(jìn)行歸類的時(shí)候,都會(huì)將一個(gè)Trash對(duì)象插入bin。<br>
通過前面那些例子的學(xué)習(xí),DynaTrash類的大多數(shù)部分都應(yīng)當(dāng)非常熟悉了。這一次,我們不再將新的Trash對(duì)象置入類型Vector的一個(gè)bin內(nèi)。由于bin的類型為TypeMap,所以將垃圾(Trash)丟進(jìn)垃圾筒(Bin)的時(shí)候,TypeMap的內(nèi)部歸類機(jī)制會(huì)立即進(jìn)行適當(dāng)?shù)姆诸悺T赥ypeMap里遍歷并對(duì)每個(gè)獨(dú)立的Vector進(jìn)行操作,這是一件相當(dāng)簡(jiǎn)單的事情:<br>
<br>
954頁(yè)程序<br>
<br>
就象大家看到的那樣,新類型向系統(tǒng)的加入根本不會(huì)影響到這些代碼,亦不會(huì)影響TypeMap中的代碼。這顯然是解決問題最圓滿的方案。盡管它確實(shí)嚴(yán)重依賴RTTI,但請(qǐng)注意散列表中的每個(gè)鍵-值對(duì)都只查找一種類型。除此以外,在我們?cè)黾右环N新類型的時(shí)候,不會(huì)陷入“忘記”向系統(tǒng)加入正確代碼的尷尬境地,因?yàn)楦揪蜎]有什么代碼需要添加。<br>
<br>
16.9 總結(jié)<br>
從表面看,由于象TrashVisitor.java這樣的設(shè)計(jì)包含了比早期設(shè)計(jì)數(shù)量更多的代碼,所以會(huì)留下效率不高的印象。試圖用各種設(shè)計(jì)方案達(dá)到什么目的應(yīng)該是我們考慮的重點(diǎn)。設(shè)計(jì)范式特別適合“將發(fā)生變化的東西與保持不變的東西隔離開”。而“發(fā)生變化的東西”可以代表許多種變化。之所以發(fā)生變化,可能是由于程序進(jìn)入一個(gè)新環(huán)境,或者由于當(dāng)前環(huán)境的一些東西發(fā)生了變化(例如“用戶希望在屏幕上當(dāng)前顯示的圖示中添加一種新的幾何形狀”)?;蛘呔拖蟊菊旅枋龅哪菢?,變化可能是對(duì)代碼主體的不斷改進(jìn)。盡管廢品分類以前的例子強(qiáng)調(diào)了新型Trash向系統(tǒng)的加入,但TrashVisitor.java允許我們方便地添加新功能,同時(shí)不會(huì)對(duì)Trash結(jié)構(gòu)造成干擾。TrashVisitor.java里確實(shí)多出了許多代碼,但在Visitor里添加新功能只需要極小的代價(jià)。如果經(jīng)常都要進(jìn)行此類活動(dòng),那么多一些代碼也是值得的。<br>
變化序列的發(fā)現(xiàn)并非一件平常事;在程序的初始設(shè)計(jì)出臺(tái)以前,那些分析家一般不可能預(yù)測(cè)到這種變化。除非進(jìn)入項(xiàng)目設(shè)計(jì)的后期,否則一些必要的信息是不會(huì)顯露出來的:有時(shí)只有進(jìn)入設(shè)計(jì)或最終實(shí)現(xiàn)階段,才能體會(huì)到對(duì)自己系統(tǒng)一個(gè)更深入或更不易察覺需要。添加新類型時(shí)(這是“回收”例子最主要的一個(gè)重點(diǎn)),可能會(huì)意識(shí)到只有自己進(jìn)入維護(hù)階段,而且開始擴(kuò)充系統(tǒng)時(shí),才需要一個(gè)特定的繼承結(jié)構(gòu)。<br>
通過設(shè)計(jì)范式的學(xué)習(xí),大家可體會(huì)到最重要的一件事情就是本書一直宣揚(yáng)的一個(gè)觀點(diǎn)——多形性是OOP(面向?qū)ο蟪绦蛟O(shè)計(jì))的全部——已發(fā)生了徹底的改變。換句話說,很難“獲得”多形性;而一旦獲得,就需要嘗試將自己的所有設(shè)計(jì)都造型到一個(gè)特定的模子里去。<br>
設(shè)計(jì)范式要表明的觀點(diǎn)是“OOP并不僅僅同多形性有關(guān)”。應(yīng)當(dāng)與OOP有關(guān)的是“將發(fā)生變化的東西同保持不變的東西分隔開來”。多形性是達(dá)到這一目的的特別重要的手段。而且假如編程語言直接支持多形性,那么它就顯得尤其有用(由于直接支持,所以不必自己動(dòng)手編寫,從而節(jié)省大量的精力和時(shí)間)。但設(shè)計(jì)范式向我們揭示的卻是達(dá)到基本目標(biāo)的另一些常規(guī)途徑。而且一旦熟悉并掌握了它的用法,就會(huì)發(fā)現(xiàn)自己可以做出更有創(chuàng)新性的設(shè)計(jì)。<br>
由于《Design Patterns》這本書對(duì)程序員造成了如此重要的影響,所以他們紛紛開始尋找其他范式。隨著的時(shí)間的推移,這類范式必然會(huì)越來越多。JimCoplien(http://www.bell-labs.com/~cope主頁(yè)作者)向我們推薦了這樣的一些站點(diǎn),上面有許多很有價(jià)值的范式說明:<br>
http://st-www.cs.uiuc.edu/users/patterns<br>
http://c2.com/cgi/wiki<br>
http://c2.com/ppr<br>
http://www.bell-labs.com/people/cope/Patterns/Process/index.html<br>
http://www.bell-labs.com/cgi-user/OrgPatterns/OrgPatterns<br>
http://st-www.cs.uiuc.edu/cgi-bin/wikic/wikic<br>
http://www.cs.wustl.edu/~schmidt/patterns.html<br>
http://www.espinc.com/patterns/overview.html<br>
同時(shí)請(qǐng)留意每年都要召開一屆權(quán)威性的設(shè)計(jì)范式會(huì)議,名為PLOP。會(huì)議會(huì)出版許多學(xué)術(shù)論文,第三屆已在1997年底召開過了,會(huì)議所有資料均由Addison-Wesley出版。<br>
<br>
16.10 練習(xí)<br>
(1) 將SingletonPattern.java作為起點(diǎn),創(chuàng)建一個(gè)類,用它管理自己固定數(shù)量的對(duì)象。<br>
(2) 為TrashVisitor.java添加一個(gè)名為Plastic(塑料)的類。<br>
(3) 為DynaTrash.java同樣添加一個(gè)Plastic(塑料)類。</p>
</table>
<p align="center"><script src="../../2.js"></script></a>
</body>
</html>
?? 快捷鍵說明
復(fù)制代碼
Ctrl + C
搜索代碼
Ctrl + F
全屏模式
F11
切換主題
Ctrl + Shift + D
顯示快捷鍵
?
增大字號(hào)
Ctrl + =
減小字號(hào)
Ctrl + -