上一次主要介紹了幾個(gè)創(chuàng)建型的設(shè)計(jì)模式AbstractFactroy,F(xiàn)actoryMethod和Singliton。它們的共同的特點(diǎn),都是用來(lái)創(chuàng)建對(duì)象的。這次接下來(lái)的內(nèi)容,涉及到的是幾個(gè)結(jié)構(gòu)型的模式。所謂結(jié)構(gòu)型模式,就是用來(lái)解決在創(chuàng)建系統(tǒng)結(jié)構(gòu)的過(guò)程中,通過(guò)對(duì)類或者對(duì)象進(jìn)行合理有效的組合,以獲得更大的結(jié)構(gòu)的方法。這兒主要講到了Bridge模式和Decorator模式。對(duì)于Bridge模式可能需要更多的理解,因?yàn)樗诤艽蟪潭壬险f(shuō),例示了設(shè)計(jì)模式的基本的設(shè)計(jì)思路和原則。 當(dāng)初Java剛剛推出來(lái)的時(shí)候,AWT可是一個(gè)比較熱的話題,雖然現(xiàn)在有被Swing取代的趨勢(shì)。但是我一直都覺(jué)得AWT也有其優(yōu)勢(shì),至少它使用的本地代碼就要比Swing快上許多,而且,可以為用戶提供熟悉的本地操作系統(tǒng)界面。如果在Windows XP中運(yùn)行基于AWT的程序的話,XP中絢爛多變的界面Theme可以輕易應(yīng)用到AWT程序中,而Swing就不行了,因?yàn)锳WT所調(diào)用的是本帶代碼,使用的是本地的窗體控件。當(dāng)然,Swing也有其好處,不可一概而論。 簡(jiǎn)單來(lái)講,AWT提供對(duì)程序員的是對(duì)窗體界面系統(tǒng)的抽象,而在內(nèi)部實(shí)現(xiàn)中,針對(duì)每一種操作系統(tǒng),分別有不同實(shí)現(xiàn),這就是同位體(Peer)的概念。當(dāng)程序員調(diào)用AWT對(duì)象時(shí),調(diào)用被轉(zhuǎn)發(fā)到對(duì)象所對(duì)應(yīng)的一個(gè)Peer上,在由Peer調(diào)用本地對(duì)象方法,完成對(duì)象的顯示。例如,如果你使用AWT創(chuàng)建了一個(gè)Menu類的實(shí)例,那么在程序運(yùn)行時(shí)會(huì)創(chuàng)建一個(gè)菜單同位體的實(shí)例,而由創(chuàng)建的同位體的來(lái)實(shí)際執(zhí)行菜單的現(xiàn)實(shí)和管理。不同的系統(tǒng),有不同的同位體實(shí)現(xiàn),Solaris JDK將產(chǎn)生一個(gè)Motif菜單的同位體,Windows下的JDK將產(chǎn)生一個(gè)Windows的菜單的同位體,等等。同位體的使用,使得交叉平臺(tái)窗口工具的開(kāi)發(fā)變得極為迅速,因?yàn)橥惑w的使用可以避免重新實(shí)現(xiàn)本地窗口控件中已經(jīng)包含的方法。 圖九:AWT中的組件和其對(duì)等體 ![]() 實(shí)際上,從設(shè)計(jì)的角度來(lái)看,這是一個(gè)抽象和實(shí)現(xiàn)分離的過(guò)程--AWT是抽象,同位體是實(shí)現(xiàn),抽象和實(shí)現(xiàn)各自成為一個(gè)對(duì)象體系,它們由一個(gè)橋連接起來(lái),可以各自發(fā)展各自的對(duì)象層次,而不必顧慮另一方面。這就是Bridge模式所提供的思想。Bridge模式更可以提供在各個(gè)不同的實(shí)現(xiàn)中動(dòng)態(tài)的進(jìn)行切換,而不必從新編譯程序。 通常,Bridge模式和AbstractFactory模式一起工作,由AbstractFactory來(lái)創(chuàng)建一個(gè)具體實(shí)現(xiàn)的對(duì)象體系。特殊的,當(dāng)只有一個(gè)實(shí)現(xiàn)的時(shí)候,可以將Implementor抽象類去掉。這樣,在抽象和實(shí)現(xiàn)之間建立起了一一對(duì)應(yīng)的關(guān)系,但這并不損害Bridge模式的內(nèi)涵。這被稱為退化了的Bridge模式。 很多時(shí)候,Abstraction層次和Implementor層次之間的方法都不是一一對(duì)應(yīng)的,也就是說(shuō),在Abstraction和Implementor之不是簡(jiǎn)單的的消息轉(zhuǎn)發(fā)。通常,我們會(huì)將Abstraction作為一個(gè)抽象類(而不是接口)來(lái)實(shí)現(xiàn)。在Implementor層次中定義底層的,或者稱之為原子方法,而在Abstraction層次中定義一些中高層的基于原子方法的抽象方法。這樣,就能更為清晰的劃分Abstraction和Implementor,類的結(jié)構(gòu)也更為清晰。 圖十:Bridge模式對(duì)系統(tǒng)的劃分 ![]() 下面,我們來(lái)看一個(gè)Bridge模式的具體應(yīng)用??紤]這樣的一個(gè)問(wèn)題,需要生成一份報(bào)告,但是報(bào)告的格式并沒(méi)有確定,可能是HTML文件,也可能是純ASCII文本。報(bào)告本身也可能分為很多種,財(cái)務(wù)報(bào)表,貨物報(bào)表,等等問(wèn)題很簡(jiǎn)單,用繼承也較容易實(shí)現(xiàn),因?yàn)橄嗷ブg的組合關(guān)系并不是很多。但是,我們現(xiàn)在需要用Bridge的觀點(diǎn)來(lái)看問(wèn)題。 在Bridge模式中,使用一個(gè)Report類來(lái)描敘一個(gè)報(bào)告的抽象,用一個(gè)Reporter類來(lái)描敘Report的實(shí)現(xiàn),它的子類有HTMLReporter和ASCIIReporter,用來(lái)分別實(shí)現(xiàn)HTML格式和ASCII格式的報(bào)告。在Report層次下面,有具體的一個(gè)StockListReport子類,用來(lái)表示貨物清單報(bào)告。
實(shí)際上,Bridge模式是一個(gè)很強(qiáng)大的模式,可以應(yīng)用在很多方面。其基本思想:分離抽象和實(shí)現(xiàn),是設(shè)計(jì)模式的基礎(chǔ)之一。正如GOF所提到的:"找到變化的部分,并將其封裝起來(lái)";"更多的考慮用對(duì)象組合機(jī)制,而不是用對(duì)象繼承機(jī)制"。Bridge模式很好的體現(xiàn)了這幾點(diǎn)。
在使用Java中的IO類庫(kù)的時(shí)候,是不是快要被它那些功能相似,卻又絕對(duì)可稱得上龐雜的類搞得要發(fā)瘋了?或許你很不明白為什么要做這么多功能相似的幾十個(gè)類出來(lái),這就是Decorator模式將要告訴你的了。 在IO處理中,Java將數(shù)據(jù)抽象為流(Stream)。在IO庫(kù)中,最基本的是InputStream和OutputStream兩個(gè)分別處理輸出和輸入的對(duì)象(為了敘述簡(jiǎn)便起見(jiàn),這兒只涉及字節(jié)流,字符流和其完全相似),但是在InputStream和OutputStream中之提供了最簡(jiǎn)單的流處理方法,只能讀入/寫(xiě)出字符,沒(méi)有緩沖處理,無(wú)法處理文件,等等。它們只是提供了最純粹的抽象,最簡(jiǎn)單的功能。 如何來(lái)添加功能,以處理更為復(fù)雜的事情呢?你可能會(huì)想到用繼承。不錯(cuò),繼承確實(shí)可以解決問(wèn)題,但是繼承也帶來(lái)更大的問(wèn)題,它對(duì)每一個(gè)功能,都需要一個(gè)子類來(lái)實(shí)現(xiàn)。比如,我先實(shí)現(xiàn)了三個(gè)子類,分別用來(lái)處理文件,緩沖,和讀入/寫(xiě)出數(shù)據(jù),但是,如果我需要一個(gè)既能處理文件,又具有緩沖功能的類呢?這時(shí)候又必須在進(jìn)行一次繼承,重寫(xiě)代碼。實(shí)際上,僅僅這三種功能的組合,就已經(jīng)是一個(gè)很大的數(shù)字,如果再加上其它的功能,組合起來(lái)的IO類庫(kù),如果只用繼承來(lái)實(shí)現(xiàn)的話,恐怕你真的是要被它折磨瘋了。 圖六:JDK中IO流的類層次 ![]() Decorator模式可以解決這個(gè)問(wèn)題。Decorator字面的意思是裝飾的意思,在原有的基礎(chǔ)上,每添加一個(gè)裝飾,就可以增加一種功能。這就是Decorator的本意。比如,對(duì)于上面的那個(gè)問(wèn)題,只需要三個(gè)Decorator類,分別代表文件處理,緩沖和數(shù)據(jù)讀寫(xiě)三個(gè)功能,在此基礎(chǔ)上所衍生的功能,都可以通過(guò)添加裝飾來(lái)完成,而不必需要繁雜的子類繼承了。更為重要的是,比較繼機(jī)制承而言,Decorator是動(dòng)態(tài)的,可以在運(yùn)行時(shí)添加或者去除附加的功能,因而也就具有比繼承機(jī)制更大的靈活性。 上面就是Decorator的基本思想,下面的是Decorator模式的靜態(tài)結(jié)構(gòu)圖: 圖七:Decorator模式的類圖 ![]() 可以看到,一個(gè)Decorator與裝飾的Subject對(duì)象有相同的接口,并且除了接口中給出的方法外,每個(gè)Decorator均有自己添加的方法,來(lái)添加對(duì)象功能。每個(gè)Decorator均有一個(gè)指向Subject對(duì)象的引用,附加的功能被添加在這個(gè)Subject對(duì)象上。而Decorator對(duì)象本身也是一個(gè)Subject對(duì)象,因而它也能夠被其他的Decorator所修飾,提供組合的功能。 在Java IO操作中,經(jīng)常可以看到諸如如下的語(yǔ)句:
多個(gè)的Decorator被層疊在一起,最后得到一個(gè)功能強(qiáng)大的流。既能夠被緩沖,又能夠得到行數(shù),這就是Decorator的威力! 不僅僅如此,Java中的IO還允許你引入自定義的Decorator,來(lái)實(shí)現(xiàn)自己想要的功能。在良好的設(shè)計(jì)背景下,這做起并不復(fù)雜,只需要4步:
就這樣,你就可以無(wú)限的擴(kuò)展IO的功能了。 在了解了IO中的Decorator后,我們?cè)賮?lái)看一個(gè)Decorator模式應(yīng)用的具體的例子。這個(gè)例子原本是出現(xiàn)在GOF書(shū)中的,這兒稍作改動(dòng),引來(lái)示例。 在一個(gè)圖形用戶界面(GUI)中,一個(gè)組件有時(shí)候需要用到邊框或者滾動(dòng)條,而有時(shí)候又不需要,有時(shí)候可能兩者都要用到。當(dāng)需要?jiǎng)討B(tài)的去處或者添加職能的時(shí)候,就可以考慮使用Decorator模式了。這兒對(duì)于一個(gè)VisualComponent組件對(duì)象,我們引入了兩個(gè)Decorator類:BoderDecorator和ScrollDecorator,分別用來(lái)為組件添加邊框和處理滾動(dòng)。程序類圖如下: 圖八:Decorator模式的應(yīng)用例子 ![]() 程序?qū)懙煤芎?jiǎn)單,沒(méi)有包括具體的代碼,只是有一個(gè)可以運(yùn)行的框架以供參考。代碼如下:
Decorator確實(shí)能夠很好的緩解當(dāng)功能組合過(guò)多時(shí)子類繼承所能夠帶來(lái)的問(wèn)題。但是在得到很大的靈活性的同時(shí),Decorator在使用時(shí)也表現(xiàn)得較為復(fù)雜。看看僅僅為了得到一個(gè)IO流,除了要?jiǎng)?chuàng)建核心的流外,還要為其加上各種各樣的裝飾類,這使得代碼變得復(fù)雜而難懂。有幾個(gè)人一開(kāi)始時(shí)沒(méi)有被Java的IO庫(kù)嚇一跳呢? |
|
來(lái)自: 木有銀 > 《Java設(shè)計(jì)模式》