第14章-原則和模式_第1頁
第14章-原則和模式_第2頁
第14章-原則和模式_第3頁
已閱讀5頁,還剩21頁未讀 繼續(xù)免費(fèi)閱讀

下載本文檔

版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請(qǐng)進(jìn)行舉報(bào)或認(rèn)領(lǐng)

文檔簡(jiǎn)介

1、第14章 原則和模式為了有效地使用像 UML 這樣的設(shè)計(jì)表示法,只是掌握不同類型的語法和語義是不夠 的。形式表示法的可用性并不能保證會(huì)很好地使用這些表示法, 好的設(shè)計(jì)和不好的設(shè)計(jì)都可 以用 UML 表達(dá)。當(dāng)然,好的設(shè)計(jì)和不好的設(shè)計(jì)之間的不同特征是很難用純粹的形式詞語描述的,很可 能在任何完備程度上也不可能做到。 推薦某些方法, 保證設(shè)計(jì)師做出的設(shè)計(jì)一定是好的設(shè)計(jì) 也非常困難。 然而現(xiàn)在已經(jīng)有了面向?qū)ο蠼:驮O(shè)計(jì)的大量經(jīng)驗(yàn), 使我們可以更好地理解怎 樣做可能會(huì)使設(shè)計(jì)成功或不成功。面向?qū)ο笤O(shè)計(jì)師們積累的經(jīng)驗(yàn)可以分為兩個(gè)不同的范疇。 一類是一些廣泛認(rèn)可的高級(jí)設(shè) 計(jì)原則。 這些原則描述了設(shè)計(jì)應(yīng)當(dāng)具有的

2、或者應(yīng)當(dāng)避開的值得注意的性質(zhì)。 對(duì)于依據(jù)這些原 則的基本原理所指出的設(shè)計(jì)特征建立的系統(tǒng),經(jīng)驗(yàn)證實(shí),可以預(yù)知其結(jié)果。這些高級(jí)原則是很重要的,但是對(duì)于試圖針對(duì)具體應(yīng)用建模的設(shè)計(jì)師,卻幾乎不能提 供可操作的指導(dǎo)。 針對(duì)這些情況, 需要一些記實(shí)性地描述不同種類的設(shè)計(jì)知識(shí), 這些知識(shí)更 關(guān)注特定的問題和解決這些問題的策略。 當(dāng)前在 設(shè)計(jì)模式 方面的工作就是通過識(shí)別共同的建 模問題,并對(duì)這些問題提供經(jīng)過驗(yàn)證的解決方案的方式,滿足這種需要。本章將討論若干已知的廣泛接受的面向?qū)ο笤O(shè)計(jì)的原則, 然后介紹設(shè)計(jì)模式的概念。 模 式的用法將通過考慮對(duì)本書前面章節(jié)中出現(xiàn)的程序的修改予以介紹。14.1 開-閉原則開(放)

3、 -(封)閉原則是 Bertrand Meyer 1988 年在他的有影響的著作 面向?qū)ο蟮能浖?gòu)造 中闡述的。 這個(gè)原則關(guān)注的是系統(tǒng)內(nèi)部改變的影響, 特別是最大限度地使模塊免受 它所使用的其它模塊改變的影響的辦法??紤]系統(tǒng)中一個(gè)模塊使用另一個(gè)模塊提供的服務(wù)的情況。通常稱前一個(gè)模塊為 客戶(client ),后一個(gè)模塊為 供應(yīng)者 ( supplier )。雖然這兩個(gè)概念有更廣泛的使用,本章詳細(xì)考 慮的只是用于類之間的關(guān)系, 即可以用 UML 的使用依賴建模的關(guān)系。 圖 14.1 表示了這種情 況。圖14.1客戶類和供應(yīng)者類之間的使用依賴如果一個(gè)模塊不受進(jìn)一步改變的影響,則稱此模塊是關(guān)閉的。這意

4、味著,客戶模塊可以放心地使用該模塊而無須擔(dān)心該模塊的改變會(huì)使客戶模塊也必須改變。關(guān)閉一個(gè)模塊是有好處的,因?yàn)檫@意味著這個(gè)模塊以后可以作為系統(tǒng)的一個(gè)穩(wěn)定的構(gòu)件使用,它不再受到進(jìn)一步改變的影響,而這種改變將會(huì)反過來影響到設(shè)計(jì)的其他部分。如果一個(gè)模塊仍然是可以擴(kuò)展的,用Meyer的詞語,稱該模塊是開放的。擴(kuò)展一個(gè)模塊意味著增加該模塊的能力或擴(kuò)展它的功能。有開放模塊是有好處的,因?yàn)檫@樣使擴(kuò)展和修改系統(tǒng)成為可能。由于系統(tǒng)需求很少是穩(wěn)定不變的,容易擴(kuò)展模塊的能力是降低系統(tǒng)維護(hù)費(fèi)用的一個(gè)重要方面。開-閉原則說明,開發(fā)者應(yīng)當(dāng)力求使所設(shè)計(jì)的模塊既是開放的同時(shí)又是封閉的。如上所述,從既開放又封閉的模塊可以得到重要

5、的好處。然而初看起來,開-閉原則似乎有些似是而非,因?yàn)楹茈y設(shè)想,一個(gè)模塊怎么可能既是開放的同時(shí)又是封閉的。如果“開放”的定義是指能夠改變一個(gè)模塊,那么一個(gè)模塊既是開的又是閉的是有矛盾的。然而“開放”的定義只是說一個(gè)模塊應(yīng)當(dāng)是可以擴(kuò)展的。為了避免矛盾就必須找出種辦法,可以擴(kuò)展該模塊而又不改變?cè)撃K。解決這個(gè)問題的一般方案是區(qū)分一個(gè)模塊的接口和它的實(shí)現(xiàn)。如果一個(gè)模塊的這兩個(gè)方面可以分離,使客戶模塊僅依賴它的供應(yīng)者模塊的接口, 那么供應(yīng)者模塊實(shí)現(xiàn)的修改就不 會(huì)影響客戶模塊。面向?qū)ο蟪绦蛟O(shè)計(jì)語言提供了許多方法, 使一個(gè)類的接口可以與它的實(shí)現(xiàn) 區(qū)別開來,本節(jié)將對(duì)支持開-閉原則需要的相應(yīng)的機(jī)制,做一簡(jiǎn)要的

6、描述和評(píng)價(jià)。14. 1. 1數(shù)據(jù)抽象使用數(shù)據(jù)抽象的意圖,是通過使實(shí)現(xiàn)細(xì)節(jié)對(duì)客戶代碼不可見的辦法,將數(shù)據(jù)類型或者類與它的實(shí)現(xiàn)相分離。這樣,可以設(shè)想數(shù)據(jù)抽象能夠構(gòu)造一個(gè)既是開放同時(shí)又是封閉的模塊。在面向?qū)ο蟪绦蛟O(shè)計(jì)語言中,數(shù)據(jù)抽象是通過指定類的每個(gè)特征的訪問級(jí),例如“公有的”或“私有的”提供的。圖 14.2通過對(duì)供應(yīng)者類的特征定義典型的訪問級(jí),表示了一般的客 戶-供應(yīng)者關(guān)系。圖14.2使用數(shù)據(jù)抽象的客戶-供應(yīng)者關(guān)系訪問級(jí)在UML中也稱為 可見性,它指明了客戶可以看到一個(gè)類的哪些特征。圖14.2中供應(yīng)者類中的操作聲明為公有的因此客戶可以看到,而屬性是私有的因而是不可見的。從客戶的視角看,類的接口是僅

7、有的可見的特征。如果可見的接口保持不變,不可見的特征可以改變、去掉或增加,都不會(huì)對(duì)客戶產(chǎn)生影響。例如,在Java中,圖14.2中的供應(yīng)者類可以如下實(shí)現(xiàn)。i c czlaB-a Suppl private lilt attr ifcute ;public void 口peration (J -| / / 1 irp le menizsi t Lon o f crps irat ioniI 這個(gè)類中公有方法的實(shí)現(xiàn)可以改變而不會(huì)對(duì)客戶類有任何影響,類似地,支持該類的 方法實(shí)現(xiàn)所需要的私有域也可以增加或去掉。為了避免影響客戶,必須保持不變的只是由該類公有方法的名稱和特征標(biāo)記(sig nature)組成

8、的可見接口。然而實(shí)現(xiàn)開-閉原則的這種方式有許多局限。從根本上講,由于系統(tǒng)的修改要求改變客 戶類的代碼,客戶模塊在技術(shù)上是不可能關(guān)閉的。如果更實(shí)質(zhì)的目標(biāo)可以達(dá)到,這種改變可以作為字面上的違反來看待,而不是違背了開閉原則的精神實(shí)質(zhì),但是數(shù)據(jù)抽象方式還存在另外的更本質(zhì)的問題。首先,雖然在Java中私有域可以被增加到類中而不會(huì)影響客戶,但并不是所有程序設(shè) 計(jì)環(huán)境都是如此。例如,在C+中類的定義典型地是由頭文件和實(shí)現(xiàn)文件分擔(dān)的,頭文件實(shí)際上是被合并到客戶模塊中去的。這樣,對(duì)頭文件的任何改變,例如,增加一個(gè)新的域,就 需要重新編譯客戶模塊,即使這種改變對(duì)客戶模塊是不可見的。如果開-閉原則的實(shí)現(xiàn)可以做到獨(dú)立

9、于語言,是更可取的。其次,在數(shù)據(jù)抽象方式中,客戶模塊所需要的接口仍處在隱含的狀態(tài)??蛻艨赡苁褂霉?yīng)者提供給他的可見到的所有特征,但也可能并不需要。實(shí)際上一個(gè)模塊的不同客戶或許使用的只是一個(gè)模塊可見接口的不同子集。這就很難準(zhǔn)確地知道,對(duì)一個(gè)模塊的哪些改變會(huì)影響給定的客戶。通過將客戶模塊所需要的接口用文檔明確地加以描述,將會(huì)改進(jìn)文檔的編制和可維護(hù)性。14. 1. 2抽象接口類實(shí)現(xiàn)開-閉原則的另一種方式是使用抽象接口類。圖14.3的類圖表示了用這種技術(shù)設(shè)計(jì)的一般結(jié)構(gòu)。圖14.3使用抽象接口類的客戶-供應(yīng)者關(guān)系這里抽象供應(yīng)者類定義了供應(yīng)者類的接口。由于抽象供應(yīng)者是一個(gè)抽象類,所以它沒 有定義屬性。供應(yīng)

10、者類的實(shí)現(xiàn)則交給定義了必需的屬性并實(shí)現(xiàn)了抽象類所聲明的功能的一個(gè) 具體子類。該客戶類聲明了它對(duì)接口類的依賴,接口類所包含的實(shí)現(xiàn)細(xì)節(jié),如果有的話,也只是已知不會(huì)再修改的細(xì)節(jié)。在Java中,抽象供應(yīng)者類可以聲明為由具體供應(yīng)者類擴(kuò)展的抽象類。具體類定義被選擇的實(shí)現(xiàn)所必需的屬性并定義所有必需的成員函數(shù)。puplic apetractAfetractSupplierIpublic abstract void operationf/?卜publ-icCo-acreLaSuppLiex cxL-enduppl i-ci:Iprivate int attribute j-IjllLILc void opera

11、tion() H I mp 1 e irlEtl tat loti O f ope rat Lon*從開-閉原則的觀點(diǎn)看,這個(gè)實(shí)現(xiàn)最重要的是對(duì)三個(gè)類之間依賴關(guān)系的影響。正如第11章所闡述的,泛化關(guān)系引起子類和超類之間的依賴性。因而圖14.3中三個(gè)類之間的使用依賴可以用圖14.4予以表示。Ii ©use葉iii圖14.4帶有抽象接口類的依賴圖14.4清楚地表明,客戶類并不依賴于具體供應(yīng)者類,結(jié)果是具體類的任何方面都可以改變而不會(huì)對(duì)客戶模塊產(chǎn)生影響。應(yīng)當(dāng)記住,我們這里講的是編譯時(shí)的依賴。典型地,在運(yùn)行時(shí)客戶可以保持或操縱對(duì) 一個(gè)具體供應(yīng)者類的實(shí)例的引用,但這個(gè)引用是保存在一個(gè)被聲明為對(duì)抽

12、象供應(yīng)者的引用的域中。由于引用的多態(tài)特性,這個(gè)域并不限于保持對(duì)該抽象供應(yīng)者類的實(shí)例的引用。因此,這個(gè)抽象供應(yīng)者類,至少在它可以被進(jìn)一步增加的子類所擴(kuò)展的意義上,是開放模塊的一個(gè)例子。新增加的子類可以提供該接口的另一種可供選擇的實(shí)現(xiàn),或者增加系統(tǒng)中以前沒有的特征。例如,在預(yù)約系統(tǒng)中的預(yù)約類展示了可擴(kuò)展性的這個(gè)特征。另外的子類可以增加進(jìn)來使系統(tǒng)能夠處理新種類的預(yù)約,但這并不會(huì)影響任何客戶模塊, 譬如餐館或預(yù)約系統(tǒng)。抽象供應(yīng)者類也是關(guān)閉的嗎?肯定地說,它比圖 14.2中的供應(yīng)者類更關(guān)閉,在圖14.2中,對(duì)供應(yīng)者對(duì)象實(shí)現(xiàn)的典型改變是對(duì)它的具體子類做出的而不是對(duì)抽象供應(yīng)者本身。然而,隨著系統(tǒng)的演進(jìn),可能

13、需要改變接口,這將要求改變抽象類。你可能會(huì)問,是否有方法關(guān)閉一個(gè)模塊,以抵御這種改變。這個(gè)題目將在14.3節(jié)考慮。使用抽象接口類,如圖 14.3所闡明的,是面向?qū)ο笤O(shè)計(jì)的基本技術(shù),在本章后面介紹 的模式中將會(huì)反復(fù)使用這些例子。14.2無具體超類14.1節(jié)討論了抽象接口類在提供滿足開-閉原則的軟件模塊中的效用。 相關(guān)的原則表明,在泛化關(guān)系中所有子類應(yīng)當(dāng)是具體的,或者反過來說,在泛化層次中所有非葉類應(yīng)當(dāng)是抽象的。這個(gè)原則可以概括為一句話,“無具體超類”。這個(gè)原則的基本原理可能最適合通過例子來理解。假定銀行正在實(shí)現(xiàn)若干個(gè)類作為不 同類型賬戶的模型,如在 8.5節(jié)所考慮的例子,這個(gè)模型的最初版本只定義

14、了一個(gè)表示活期 賬戶的類,具有通常的取款和存款操作。后來該銀行又增加了儲(chǔ)蓄賬戶并增加了根據(jù)賬戶余額支付利息的功能。由于這些新賬這樣戶可以共享活期賬戶的大部分功能,決定將儲(chǔ)蓄賬戶類作為活期賬戶類的特化來定義,就可以繼承共享的功能。圖14.5表示了這種情況。圖14.5具體超類的例子這個(gè)設(shè)計(jì)明顯地違背了“無具體超類”原則,因?yàn)閳D 14.5中的活期賬戶類既是具體類 又是超類。隨著設(shè)計(jì)的演進(jìn),違反這個(gè)原則可能會(huì)導(dǎo)致嚴(yán)重的問題,我們將通過接著開發(fā)上述例子說明。例如,假設(shè)最初定義的取款操作不允許賬戶透支。然而這種限制使很行活期賬戶得不到客戶的歡迎,銀行決定允許活期賬戶透支而不允許儲(chǔ)蓄賬戶透支??墒且薷膱D1

15、4.5的設(shè)計(jì)以提供這種功能并不容易。例如,簡(jiǎn)單地修改在活期賬戶中定義的取款操作以允許透支,并不是正確的解決辦法。由于這個(gè)操作已被儲(chǔ)蓄賬戶類繼承,這將會(huì)導(dǎo)致也允許儲(chǔ)蓄賬戶中的提款操作透支的不正確結(jié)果。為了提供原有的功能,在儲(chǔ)蓄賬戶中覆蓋這個(gè)取款操作是可以的,但這是一個(gè)很勉強(qiáng)的不自然的解決辦法。更嚴(yán)重的后果是,這兩個(gè)類實(shí)際執(zhí)行的取款操作的代碼的重復(fù)。在這個(gè)例子中這種重復(fù)問題不大,但一般地,代碼的重復(fù)是設(shè)計(jì)錯(cuò)誤的一個(gè)主要征兆。另一種可能是在活期賬戶中定義的取款操作有辦法檢查取款操作是由運(yùn)行時(shí)的哪種類型賬戶做出的,然后選擇相應(yīng)的動(dòng)作路徑。下面一段代碼扼要地說明了這個(gè)操作的實(shí)現(xiàn)。pob-1 ic 口Iv

16、czd xj. Liidf aw (double amt if (this Lnstanceof SaviugaAc count) / Cheek i± bee 匚over Jr awnbalaLU匚e -=. a.m.b $可是這種風(fēng)格的程序設(shè)計(jì)也有嚴(yán)重的缺點(diǎn),它包括了超類 Current Account'對(duì)它的子類顯式地引用。這意味著當(dāng)增加新的子類時(shí),活期賬戶類中的代碼一般地也必須改變,使活期賬戶類在 Meyer意義下不可能關(guān)閉。由于這個(gè)原因以及其他原因,在一個(gè)系統(tǒng)的設(shè)計(jì)中 求助于這種風(fēng)格的程序設(shè)計(jì)也有明顯的缺點(diǎn)。如果一個(gè)功能只被定義在活期賬戶中,也會(huì)產(chǎn)生類似的問題。例如

17、,假定該銀行想對(duì)活期賬戶增加兌付現(xiàn)金支票功能,而這種功能對(duì)儲(chǔ)蓄賬戶不能使用。這個(gè)功能最明顯的實(shí)現(xiàn),如下所示,是檢查現(xiàn)金支票(cashCheque)的變?cè)谶\(yùn)行時(shí)的類型。void( CurreTitAccmjnt a ) ifSavinrjsflir:civunt) return ;f/ URHll cheque |在這些情況中問題的發(fā)生是由于圖 14.5中的活期賬戶類扮演了兩種角色。作為超類,它定義了所有賬戶對(duì)象必須實(shí)現(xiàn)的接口,而它又提供了該接口的缺省實(shí)現(xiàn)。在我們討論過的這些例子中,這些角色是沖突的。 特別是,當(dāng)特定的功能必須以某種方式與具體超類的實(shí)例相關(guān)時(shí),這種沖突就會(huì)發(fā)生。對(duì)這類問題的一般

18、解決辦法是遵循所有超類都應(yīng)當(dāng)是抽象的規(guī)則。這意味著,在圖14.5中所表示的設(shè)計(jì)應(yīng)當(dāng)用圖14.6所示設(shè)計(jì)取代。圖14.6應(yīng)用“無具體超類”規(guī)則這樣一來,在賬戶Acco unt'超類中的取款操作可以執(zhí)行基本的取款,而可以把檢查 儲(chǔ)蓄賬戶不能透支的代碼放在覆蓋它的函數(shù)中。為了使用公用功能,如果需要,子類也可以調(diào)用在超類中的操作實(shí)現(xiàn)。此外,由于儲(chǔ)蓄賬戶類( Savings Accou nt)不再是活期賬戶類的 子類,也不再需要像上面那樣將活期賬戶的實(shí)例傳送給cashCheque'函數(shù),也沒有必要檢查該函數(shù)的變?cè)谶\(yùn)行時(shí)的類型。14.3接口層次的解耦假定已經(jīng)按照?qǐng)D14.3所示的設(shè)計(jì)思路實(shí)

19、現(xiàn)了抽象接口類。后來又需要修改,增加一個(gè) 功能到抽象供應(yīng)者類提供的接口,以滿足新的客戶類的需要。圖14.3給出的設(shè)計(jì)看來并沒有提供直接把這個(gè)功能增加到抽象接口類的辦法。所以這 種方式仍然有需要認(rèn)真對(duì)待的問題。首先,目標(biāo)是關(guān)閉這個(gè)抽象供應(yīng)者類,因此不應(yīng)當(dāng)修改這個(gè)類。其次,這種改變可能導(dǎo)致對(duì)現(xiàn)有客戶的進(jìn)一步改變,至少會(huì)迫使對(duì)它們進(jìn)行再編譯。或者由該抽象供應(yīng)者提供一第三,新的功能還必須在所有已有的具體供應(yīng)者類中予以實(shí)現(xiàn), 個(gè)缺省實(shí)現(xiàn)。這些問題可以通過定義一個(gè)作為接口的抽象供應(yīng)者,而不是作為供應(yīng)者類層次中的基類定義的抽象供應(yīng)者來避免。 如果這樣做,具體供應(yīng)者類不再是抽象供應(yīng)者的子類, 而代替 的是提供

20、了它所定義的該接口的實(shí)現(xiàn)。在圖 14.7所示的這個(gè)新設(shè)計(jì)是圖 14.3所示的設(shè)計(jì)的 另一種選擇。圖14.7使用接口這樣就有可能做到不修改接口本身來處理擴(kuò)展抽象供應(yīng)者接口的要求,以應(yīng)對(duì)新客戶 的需要。替代的是,可以定義一個(gè)新接口,它是這個(gè)抽象供應(yīng)者接口的特化。新客戶使用這 個(gè)新接口,而原有的客戶繼續(xù)使用原來未改變的接口。這個(gè)新接口本身可以由新具體供應(yīng)者類實(shí)現(xiàn)。原有的抽象供應(yīng)者接口并未改變,所以它是一個(gè)名副其實(shí)的關(guān)閉模塊。圖14.8表示了這個(gè)結(jié)果情況。圖14.8用特化擴(kuò)展接口注意,圖14.8并沒有表示具體供應(yīng)者和新的具體供應(yīng)者類之間的泛化關(guān)系。實(shí)現(xiàn)新具體供應(yīng)者的一種方法是繼承由具體供應(yīng)者實(shí)現(xiàn)的抽象

21、供應(yīng)者的功能(冒著運(yùn)行違反無具體超類規(guī)則的風(fēng)險(xiǎn)),而另外的實(shí)現(xiàn)也是可能的。例如,可以在新具體供應(yīng)者類中包含一個(gè)對(duì)具 體供應(yīng)者實(shí)例的引用,并委派它做相應(yīng)的功能調(diào)用,或者對(duì)每件事都簡(jiǎn)單地從頭重新實(shí)現(xiàn)。因?yàn)榭蛻裟K現(xiàn)在只是間接地與具體供應(yīng)者相關(guān),不同供應(yīng)者也就不需要為了提供多態(tài)客戶接口而通過泛化相關(guān):這是由接口之間的泛化提供的。14.4 LISKOV替換原則如果使用如14.1節(jié)描述的抽象接口類,那么對(duì)于根據(jù)抽象接口類定義的接口編程的客戶模塊,會(huì)廣泛使用多態(tài)。例如,在圖14.3中,該客戶只知道抽象供應(yīng)者。這意味著,該客戶如下所示,將通過引用抽象供應(yīng)者( Abstract Supplier)類型調(diào)用供應(yīng)

22、者(Supplier )的 操作。AtstracrtEiapp I ier Fuppl ier ;supplier_ operation) 當(dāng)這段代碼運(yùn)行時(shí),此Supplier '變量并沒有包含對(duì)Abstract Supplier '的實(shí)例的引 用:因?yàn)檫@是一個(gè)抽象類,不可能有實(shí)例。代替的是它將保持一個(gè)對(duì)Concrete Supplier'類的實(shí)例的引用或者該抽象供應(yīng)者類的某個(gè)另外子類的實(shí)例的引用。如果這種多態(tài)性在程序中不會(huì)引起問題,那么,該程序語言必須保證下述為真:如果 一個(gè)客戶期望保持對(duì)類T的一個(gè)對(duì)象的引用,那么當(dāng)用T的一個(gè)特化類 S的一個(gè)對(duì)象引用替代時(shí),同樣會(huì)滿意

23、地工作。Liskov替換原則 是1987年計(jì)算機(jī)科學(xué)家 Bavbara Liskov給出一個(gè)經(jīng)典陳述后命名的。 這個(gè)原則給出了一個(gè)類型是另一個(gè)類型的子類型意味著什么的定義,在效果上提供了所需要的保證。在這個(gè)語境中該原則可以敘述如下。如果下述為真,那么類 S就正確地定義為類 T的特化:對(duì)類 S的每個(gè)對(duì)象s,存在著 類T的一個(gè)對(duì)象t,使得根據(jù)類T定義的任何程序 P的行為,如果用s替換t,其行為不會(huì) 改變。非形式地說,這就意味著,子類的實(shí)例可以替換超類的實(shí)例而不會(huì)對(duì)客戶類或模塊產(chǎn) 生任何影響。這個(gè)原則的一個(gè)推論是,超類之間的關(guān)聯(lián)也可以被它們的子類所繼承,因?yàn)閷?duì)在一個(gè)鏈接的另一端的一個(gè)對(duì)象來說,它是否

24、被鏈接到一個(gè)用子類對(duì)象代替的超類對(duì)象并沒有什么不同。雖然這個(gè)定義是用類型和子類型的語言表述的,但這個(gè)Liskov替換原則還是有效地定義了面向?qū)ο蟪绦蛟O(shè)計(jì)語言中使用的泛化概念的意義。在UML中,例如在類之間、用例之間和參與者之間所定義的各種形式的泛化,都共享這個(gè)性質(zhì),即在這樣的關(guān)系中總可以用特化的實(shí)體的實(shí)例替換更一般的實(shí)體的實(shí)例。準(zhǔn)確地說,這具體意味著什么將依賴于所考慮的實(shí)體的類型。例如,在參與者情況下 意味著,如圖4.5所示,特化參與者,作為更一般的參與者,可以參與更一般的參與者與之 交互的所有用例的交互。因此,類之間的泛化只有在超類的當(dāng)前值可以由子類的當(dāng)前值自由地替換的情況下,才能正確地使用

25、,客戶模塊不可能察覺這種替換。在任何環(huán)境,如果把子類對(duì)象用在超類對(duì)象的位置,一個(gè)程序會(huì)有不同的行為,那么就是不正確地使用了泛化。程序設(shè)計(jì)語言是在語法層上檢查可替換性的,它將檢查用這種方式定義的子類在運(yùn)行時(shí)不會(huì)發(fā)生某些種類的錯(cuò)誤,例如,向一個(gè)對(duì)象發(fā)送一個(gè)它不理解的消息。但是程序員在用這種方式實(shí)現(xiàn)派生類中的操作時(shí),總是可能由于提供了使子類實(shí)例以完全不同于超類實(shí)例的方式行為的實(shí)現(xiàn),而不自覺地?fù)p害了可替換性。在研究文獻(xiàn)中已經(jīng)做了許多努力,試圖描述更強(qiáng)的替換性概念,以保證子類提供的行為能以某種方式與超類中定義的行為兼容。但是這種要求必須謹(jǐn)慎地表述:要求子類提供與超類一樣的行為是不合適的,因?yàn)樵谠S多情況下

26、,定義一個(gè)子類的全部特征就是定義這些對(duì)象的某些類特有的行為。處理這個(gè)問題的一種方式是對(duì)泛化關(guān)系中的這些類所定義的約束之 間的關(guān)系做出明確說明。14.5交互決定結(jié)構(gòu)這個(gè)例子的基礎(chǔ)是 Robert Martin ( 1998)的一篇論文。它闡述了在預(yù)約系統(tǒng)的開發(fā)中使用的啟發(fā)方法,即根據(jù)對(duì)象之間的交互用作確定設(shè)計(jì)中的對(duì)象所屬的類之間的結(jié)構(gòu)關(guān)系。假定我們要建立一個(gè)簡(jiǎn)單的移動(dòng)電話模型,電話的接口由一組撥號(hào)按鈕,一個(gè)開始呼 叫的“發(fā)送”按鈕和一個(gè)結(jié)束呼叫的“清除”按鈕組成。一個(gè)稱為撥號(hào)器的部件與各種按鈕 交互并跟蹤當(dāng)前呼叫狀態(tài)和截止當(dāng)前已撥過的數(shù)字。蜂窩無線電處理到蜂窩網(wǎng)的連接。此外電話機(jī)還有一個(gè)微受話器

27、,一個(gè)送話器和在上面可以顯示所撥號(hào)碼的顯示屏。圖14.9給出了根據(jù)這個(gè)描述做出的一個(gè)沒有經(jīng)驗(yàn)的移動(dòng)電話設(shè)計(jì)。這個(gè)圖給出了一個(gè)直觀上清晰的移動(dòng)電話的物理結(jié)構(gòu)模型。每個(gè)部件都用類表示, 聚合用來說明各種部件都是由移動(dòng)電話類表示的這個(gè)組裝體的一部分。圖14.9移動(dòng)電話的物理模型這個(gè)設(shè)計(jì)是否適當(dāng),可以通過察看是否支持實(shí)現(xiàn)要求該電話的行為來測(cè)試。例如,考慮下述腳本,它描述了一個(gè)用戶用此電話呼叫時(shí)可能發(fā)生的基本事件流。1. 用戶輸入呼叫的電話號(hào)碼。2. 每個(gè)按下數(shù)字被添加到顯示器上。3. 用戶按下“發(fā)送”鍵。4. 無線收發(fā)器建立到網(wǎng)絡(luò)的連接并發(fā)出呼叫。5. 顯示器顯示該電話現(xiàn)在忙。6. 這次呼叫結(jié)束,用戶

28、按下“清除”鍵。7. 顯示器被清除。圖14.10表示了這個(gè)腳本從開始到建立連接為止的一個(gè)實(shí)現(xiàn)。當(dāng)用戶按下一個(gè)鍵時(shí),一個(gè)給出了所按下的那個(gè)數(shù)字的信息的消息被發(fā)送到撥號(hào)器;然后,這個(gè)數(shù)字被添加到顯示器上。當(dāng)用戶按下發(fā)送按鈕,撥號(hào)器把全部號(hào)碼發(fā)送到無線收發(fā)器,由它呼叫,一旦被連接即更新顯示器。圖14.10用移動(dòng)電話進(jìn)行呼叫從這些圖可以直接看出,圖14.9類圖中的關(guān)聯(lián)并不支持圖 14.10表示的交互路徑中被發(fā) 送的消息。為了得到一致的設(shè)計(jì), 或者改變移動(dòng)電話的靜態(tài)模型, 或者使這個(gè)腳本的實(shí)現(xiàn)與 圖14.9中的模型一致。在此例中可能更可取的是采用由圖14.10所表示的交互圖所蘊(yùn)含的結(jié)構(gòu)。雖然上述腳本的實(shí)

29、現(xiàn)也可以建立在圖14.9所示的結(jié)構(gòu)之上,但若強(qiáng)使這些對(duì)象之間的交互都通過中央移動(dòng)電話對(duì)象安排路徑,將導(dǎo)致更復(fù)雜的交互,迫使電話對(duì)象跟蹤走過交互的每個(gè)細(xì)節(jié)。更好的方式是,所采用的靜態(tài)結(jié)構(gòu)是根據(jù)為支持系統(tǒng)交互所需要的結(jié)構(gòu)而建立的靜態(tài)結(jié) 構(gòu)模型。圖14.11表示的是按照這種方式得到的移動(dòng)電話模型。圖 14.11 更好的移動(dòng)電話模型這個(gè)模型和圖 14.9 所示的模型之間最顯著的不同也許是沒有任何必要再設(shè)置一個(gè)對(duì)象 表示移動(dòng)電話本身, 因此, 移動(dòng)電話類和鏈接到電話的不同部件之間的聚合關(guān)系,在這個(gè)模型中也就不再出現(xiàn)。作為結(jié)果,也許可以說這個(gè)模型沒有像原來那個(gè)模型很好地表示現(xiàn)實(shí)世界。 然而認(rèn)為“現(xiàn) 實(shí)世界

30、” 只可能用一種方法正確地建模的觀念是過于簡(jiǎn)單化了。 這個(gè)例子的教益是, 我們建 模的對(duì)象之間的物理關(guān)系, 可能沒有提供合適的基礎(chǔ)支持系統(tǒng)功能所需要的交互。 在這種情 況,采用能更好地支持這種必須的交互的靜態(tài)模型,通常是更可取的。14.6 設(shè)計(jì)模式在任何設(shè)計(jì)活動(dòng)中都存在著某些重復(fù)遇到的典型問題。 不同設(shè)計(jì)師對(duì)這些問題設(shè)計(jì)出不 同的解決方案, 隨著設(shè)計(jì)經(jīng)驗(yàn)在實(shí)踐者之間日益廣泛地流傳, 描述了這些共同問題和解決這 些問題的方案的大量知識(shí)也在逐漸形成。 但是這些方案往往不是完全顯然的, 而且涉及到一 些不是廣為人知的職業(yè)竅門' 。使這些知識(shí)更清晰明確并可以公開地得到和利用, 顯然是有好處的。

31、 或許最重要的是這 將使沒有經(jīng)驗(yàn)的設(shè)計(jì)師可以訪問這些可以直接應(yīng)用的技術(shù)庫, 這樣會(huì)使他們擴(kuò)展相關(guān)設(shè)計(jì)技 術(shù)中的專門知識(shí)的過程更加容易。 就面向?qū)ο笤O(shè)計(jì)來說, 設(shè)計(jì)模式 已經(jīng)推薦為編篡內(nèi)行設(shè)計(jì) 師處理和解決在設(shè)計(jì)中通常遇到的特定問題的方法的一種方式。1995 年出版的由 Erich Gamma, Richard Helm, Ralph Johnson 和 John vissides 所著的 設(shè)計(jì)模式 一書,是論及這個(gè)主題的典范。這本書以“四人組”的稱號(hào)廣為人知,在本章的其 余部分也會(huì)這樣引用。設(shè)計(jì)師面對(duì)的問題來自不同層次。 在最低層,涉及的是單個(gè)類的接口或?qū)崿F(xiàn)的細(xì)節(jié)問題。 在最高層, 涉及的是系

32、統(tǒng)的整體構(gòu)架的創(chuàng)建問題。 設(shè)計(jì)模式關(guān)注的是中間層, 在這一層必須 保證局部化的特定的設(shè)計(jì)性質(zhì)。設(shè)計(jì)模式關(guān)注的典型問題可以包括下列問題。設(shè)計(jì)如何保證在系統(tǒng)內(nèi)部的一個(gè)類始終 只有一個(gè)實(shí)例被創(chuàng)建?如何建立像樹一樣的遞歸模型?如何動(dòng)態(tài)地將追加的功能增加到一 個(gè)對(duì)象?對(duì)一個(gè)設(shè)計(jì)模式提出的問題的解決方案,典型地是作為面向?qū)ο笤O(shè)計(jì)的一個(gè)不完整部 分表達(dá)的, 它由一系列交互的類組成, 它們結(jié)合在一起對(duì)所提出的問題提供了預(yù)制的解決方 案。然而一個(gè)模式通??梢员粦?yīng)用到許多情況,因此,這個(gè)解決方案描述的是一個(gè)模板,或 構(gòu)造型,當(dāng)需要時(shí)可以加以改寫或者重用的設(shè)計(jì)。14 6 1 模式的定義模式常常定義為“對(duì)一個(gè)語境中的

33、問題的解決方案” 。對(duì)面向?qū)ο笤O(shè)計(jì)中的問題的解決 方案,可以用類圖或交互圖這類規(guī)范化的表示法簡(jiǎn)明地加以描述。 但至少由于兩個(gè)原因不應(yīng) 當(dāng)認(rèn)為規(guī)范化的表示法與模式是同一件事。首先,許多模式共享類似的規(guī)范結(jié)構(gòu), 而卻又是為在完全不同的情況下使用而設(shè)計(jì)的。 一個(gè)模式不同于另一個(gè)模式的不可缺的部分是理解它所關(guān)注的問題, 即使所建議的在兩種或 多種情況下的解決方案看起來可能在形式上是類似的。其次,大多數(shù)模式可以使用在細(xì)節(jié)上可能不同的類結(jié)構(gòu)或交互,以不同方法的變種表 示或?qū)崿F(xiàn)。這樣,在細(xì)節(jié)上完全不同的設(shè)計(jì),可能事實(shí)上卻是同一模式的應(yīng)用。四人組的書給出了定義一個(gè)模式必不可少的基本要素如下。1名稱 。模式有一

34、個(gè)助記的名稱,幫助設(shè)計(jì)師記住它。命名了的模式的集合也提供了 一種行話,使設(shè)計(jì)師有可能在模式級(jí)上討論設(shè)計(jì)。2問題 。它定義了該模式可以應(yīng)用的情況。3 解決方案 。它描述了處理該問題的設(shè)計(jì)元素。這個(gè)方案常常是用適當(dāng)?shù)囊?guī)范表示法 表達(dá)的,但如上面所強(qiáng)調(diào)的,一個(gè)模式圍繞著共同思路常常有一系列變種。4效果 。這是應(yīng)用該模式的結(jié)果和權(quán)衡。如果在一個(gè)特定情況下,已經(jīng)做出了是否應(yīng) 該使用該模式的非正式?jīng)Q定,那么,知道使用一個(gè)模式的效果是絕對(duì)必要的。因此,設(shè)計(jì)模式的概念是不太規(guī)范的。這種不規(guī)范性使得當(dāng)遇到兩個(gè)模式是一樣的, 或者一個(gè)設(shè)計(jì)是否使用了一個(gè)給定的模式這類問題時(shí), 很難做出明確的回答。 模式界許多人 在

35、為這種不規(guī)范性辯護(hù),理由是模式捕捉到的是“設(shè)計(jì)見解” ,它抵制的就是不受規(guī)范性的 損害,如果定義的太狹隘,就會(huì)損害對(duì)可應(yīng)用性的理解范圍。另一方面由于沒有一個(gè)清晰的概念說明一個(gè)模式的定義實(shí)際上由哪些部分組成,要想 分辨兩位作者是否描述了相同的模式, 一個(gè)模式的變種, 或者兩個(gè)不同的模式, 還是找出一 個(gè)可用于給定情況的模式, 都是困難的。 隨著出版的模式文獻(xiàn)的擴(kuò)展, 這些問題也變得更令 人擔(dān)心。14 6 2 模式和框架模式和框架是記錄或重用某些設(shè)計(jì)元素的兩種方式,有時(shí)常會(huì)混淆。但它們?cè)谒峁┑臇|西和如何使用的意圖上有很大不同。首先,模式和框架在利用的 規(guī)模 上是不同的??蚣芏x了一個(gè)完整應(yīng)用或者

36、相關(guān)的一類應(yīng)用的構(gòu)架。 另一方面, 模式描述的是單個(gè)設(shè)計(jì)問題的解決方案, 它可以應(yīng)用到許多應(yīng)用 或框架中。其次,模式和框架有不同的 內(nèi)容 。一個(gè)模式是純粹的設(shè)計(jì)思路,它可以用不同語言以 不同方法改寫和實(shí)現(xiàn)。 框架典型地是設(shè)計(jì)和代碼的混合物, 它可以由應(yīng)用程序以各種方式擴(kuò) 展。第三,作為第二點(diǎn)的推論,模式比框架更關(guān)注 可移植性 ??蚣苁且呀?jīng)實(shí)現(xiàn)了的,雖然 并沒有必要組成一個(gè)完整的應(yīng)用, 因此常常受限于一個(gè)單一的實(shí)現(xiàn)環(huán)境。 模式是獨(dú)立于語言 的,可以在廣泛的不同情況中得到應(yīng)用。第三個(gè)概念,也帶有重用的隱含意義的,是“構(gòu)件”的概念。構(gòu)件通常考慮的是使代碼 重用并至多允許在有限程度上按規(guī)格定制。 在文

37、獻(xiàn)中已經(jīng)有了 “框架等于構(gòu)件加模式” 這種 說法,但這種說法貶低了框架在定義完整系統(tǒng)構(gòu)架中的作用。14.7 遞歸結(jié)構(gòu)在本書前幾章的例子中,我們?cè)紤]過一個(gè)對(duì)象連接到相關(guān)類的若干個(gè)對(duì)象的情況, 包括它自身所屬的類的對(duì)象, 并且需要對(duì)這些對(duì)象以完全類似的方式進(jìn)行處理。在第 2 章庫存控制系統(tǒng)中的組件就是一個(gè)例子。在這個(gè)例子中,一個(gè)組件可以包含未明確指明數(shù)目的其他對(duì)象, 其中某些是簡(jiǎn)單零件, 另一些本身又是組件。這些子組件除可以包括簡(jiǎn)單零件外,還可以依次地包括另一些組件, 這個(gè)組件的嵌套可以一直繼續(xù)下去, 直到所需要的程度。 這里在許多方面, 零件和組件都是 以完全相同的方式處理的,并且零件類和組件

38、類共享相同的接口。這種情況可以通過引入一個(gè)新類來定義共享的共同特征來建模。在第 2 章這個(gè)類稱為 Component ',圖 14.12 表示的是描述了這個(gè)構(gòu)件結(jié)構(gòu)的類圖的有關(guān)部分。CompojiPJ:!toompononts丄LPartAsseTibyoosHCDSlt j IIL%uk i Idtii c n conpofnts l,'letum 的tiy.gdCost)號(hào) 1 站 += c jm/U j圖14.12 零件和組件'構(gòu)件是一個(gè)抽象類,它描述了零件和組件的共同特征。確定構(gòu)件成本的操作是抽 象操作,它必須在每個(gè)子類中覆蓋。對(duì)于零件,此操作將從與它相關(guān)聯(lián)的目

39、錄條目(catalogueEntry)對(duì)象(圖14.12中未表示)查找該零件的價(jià)格。對(duì)于組件,此操作將迭代通過包含在該組件中的所有構(gòu)件,調(diào)用每個(gè)構(gòu)件中的價(jià)格操作,返回所有返回值的和。這些特性表示在圖14.12對(duì)這些操作所附加的注解中。組件和它所包含的構(gòu)件之間的鏈接是通過從組件類到構(gòu)件超類的關(guān)聯(lián)建模的。這里說明了一個(gè)組件可以有0個(gè)或多個(gè)構(gòu)件,每個(gè)構(gòu)件可以是零件或組件。圖14.12中的關(guān)聯(lián)不同于第2章給出的關(guān)聯(lián)在于,這里的組件被定義為它的構(gòu)件的聚件(aggregate) o女口 6.9節(jié)所說明的,這就保證了組件有著嚴(yán)格的樹結(jié)構(gòu),所以組件決不可能包含它自身,無論是直接地包含或間接地包含。零件和組件有

40、兩個(gè)重要的語義性質(zhì)使得在圖14.12中采用這種泛化是恰當(dāng)?shù)摹J紫龋鼈児蚕砉餐慕涌?,共同的超類定義了這個(gè)接口。其次,一個(gè)組件的總的結(jié)構(gòu)是一個(gè)遞歸定義的樹結(jié)構(gòu),在此結(jié)構(gòu)中,組件的內(nèi)容可能是零件或其他組件。14. 7. 1組合模式圖14.12舉例說明的結(jié)構(gòu)是經(jīng)常見到的,四人組的書中是在命名為組合(composite)的模式中記錄了它的本質(zhì)屬性。組合模式的意圖是“將對(duì)象組合成樹形結(jié)構(gòu)以表示部分整體層次結(jié)構(gòu)。組合使得用戶可以一致地處理單個(gè)對(duì)象和組合對(duì)象”。一個(gè)模式表達(dá)的解決方案典型地是用類圖記錄在文檔中的。此類圖表示了一些類可以怎樣相互交互,以提供所需要的功能。在組合情況中,組合模式的描述非常類似于

41、圖14.12所示的它的應(yīng)用,但對(duì)所參與的類給予了更一般的名稱。圖14.13中所表示的是在四人組書中所定義的組合模式的簡(jiǎn)化版本。圖12.13組合模式的結(jié)構(gòu)這個(gè)圖表示了一個(gè)遞歸結(jié)構(gòu)的構(gòu)件可以是一個(gè)葉,它將不再有它自己的子構(gòu)件,或者 是一個(gè)組合,它可以有任意多個(gè)孩子構(gòu)件。在庫存控制例子中,零件類對(duì)應(yīng)于葉類,而組件 類對(duì)應(yīng)于這里稱為組合的類。這里的構(gòu)件類定義了一個(gè)一致的接口,客戶通過此接口可以訪問和操縱組合結(jié)構(gòu)。在圖14.13中,這是用抽象操作表示的。這個(gè)接口必須在每個(gè)子類中明確地定義,在組合類中 該操作的實(shí)現(xiàn)通常將調(diào)用它的每個(gè)孩子的操作。這個(gè)操作用附加到組合類中該操作的注解指明。圖14.13的類圖也

42、定義了一個(gè)類屬結(jié)構(gòu),使類-樹結(jié)構(gòu)可以由程序來建造。希望使用這個(gè)模式的設(shè)計(jì)師可以利用這個(gè)圖,標(biāo)明應(yīng)用中對(duì)應(yīng)于葉和組合的類,并創(chuàng)建一個(gè)它們的共同超類。那么,該操作的定義就提供了一個(gè)可訪問該樹中每個(gè)節(jié)點(diǎn)的可重用的實(shí)現(xiàn)。14. 7. 2 UML中的模式設(shè)計(jì)模式可以設(shè)想為是一個(gè)可以在許多不同情況下被重用的一般目的的設(shè)計(jì)方案。圖14.13中的類并不是應(yīng)用系統(tǒng)中的類,而是更像一個(gè)占位符,在該模式的不同應(yīng)用中,它將 等同于不同的類。為了反映這個(gè)意思,在UML中這些模式是作為 參數(shù)化協(xié)作表示的。該模式將作為單- 的單元,協(xié)作,來對(duì)待,模式中的類的名稱定義為參數(shù),當(dāng)應(yīng)用該模式時(shí),可以用實(shí)際類名 取代。圖14.14

43、表示了用這種形式表示的組合模式。圖12.14作為參數(shù)化協(xié)作的組合模式用這種方式表示模式的一個(gè)優(yōu)點(diǎn)是,一個(gè)模式的應(yīng)用可以記實(shí)性地記錄在一個(gè)類圖中。圖14.15表示的是庫存控制程序和組合模式的抽象表示合在一起的一個(gè)簡(jiǎn)化的類圖。組合模式到類的連接是用模式中類的名稱標(biāo)明的,這就如實(shí)地記錄了設(shè)計(jì)中的哪些類對(duì)應(yīng)于該模式的實(shí)例化中的哪些特定角色。ConpMls !圖14.15模式應(yīng)用的文檔化14.8狀態(tài)和策略模式13.7節(jié)中描述的狀態(tài)圖的另一種實(shí)現(xiàn)可以建立在稱為“狀態(tài)(State)”的設(shè)計(jì)模式的應(yīng)用的基礎(chǔ)上,這是四人組書中的一個(gè)模式。狀態(tài)模式的意圖是 “允許一個(gè)對(duì)象在其內(nèi)部狀態(tài)改變時(shí)改變其行為。這個(gè)對(duì)象看起

44、來好像是改變了它的類?!眻D14.16給出的是概括了狀態(tài)模式的結(jié)構(gòu)的類圖。這里Con text類表示的實(shí)體展示了依賴于狀態(tài)的行為。Con text對(duì)象的不同狀態(tài)是通過狀態(tài)層次中的類表示的;每個(gè)Con text對(duì)象每次只連接到這個(gè)狀態(tài)層次中的一個(gè)狀態(tài),該狀態(tài)表示Con text的當(dāng)前狀態(tài)。圖14.16狀態(tài)模式(請(qǐng)編輯注意,此圖取自光盤,有錯(cuò)誤,應(yīng)以原書為準(zhǔn),見317頁)當(dāng)一個(gè)Con text對(duì)象接受一個(gè)請(qǐng)求時(shí),它并不試圖自己來處理這個(gè)請(qǐng)求,而是委托此狀態(tài)類中的一個(gè)操作去處理這個(gè)請(qǐng)求。由于在這個(gè)狀態(tài)類的子類中提供了該操作的不同實(shí)現(xiàn), 這個(gè)Con text對(duì)象看起來好像是提供了動(dòng)態(tài)變化的行為。圖14.

45、17表示的是與狀態(tài)模式有關(guān)的稱為“策略(Strategy)”的模式。策略模式通過用一個(gè)類的不同實(shí)例可以支持同一個(gè)操作的不同實(shí)現(xiàn)或者在運(yùn)行時(shí)改變實(shí)現(xiàn)的辦法,允許選擇一個(gè)算法的不同實(shí)現(xiàn)。這里Con text對(duì)象的接口定義了一個(gè)操作,但Co ntext類并不實(shí)現(xiàn)它。這個(gè)實(shí)現(xiàn)是由與該對(duì)象相連接的策略類的一個(gè)對(duì)象提供該實(shí)現(xiàn),即把對(duì)這個(gè)操作的調(diào)用委托給了這個(gè)策略類的對(duì)象。但是盡管有這些不同,除了類和所涉及的操作的命名之外,策略模式的結(jié)構(gòu)與狀態(tài)模式幾乎沒有什么區(qū)別。然而狀態(tài)模式和策略模式仍然被認(rèn)為是不同的模式,因?yàn)樗鼈冡槍?duì)的是不同的問題。 這個(gè)例子說明了與其說模式給出了結(jié)構(gòu)倒不如說圖給出了模式的結(jié)構(gòu)。但是,

46、在另一方面, 這也使人們對(duì)如何區(qū)別一個(gè)模式不同于另外一個(gè)模式提出了懷疑。例如,也許一個(gè)對(duì)象的不同狀態(tài)可以設(shè)想為對(duì)實(shí)現(xiàn)該對(duì)象的操作的選擇策略。還會(huì)遇到更復(fù)雜的情況,下一節(jié)將考慮一族模式,它們本質(zhì)上是同一觀念的實(shí)現(xiàn),不過在結(jié)構(gòu)上是不同的。圖14.17策略模式14.9 MVC,文檔/視圖和觀察者第5章描述的模型-視圖-控制者(MVC )架構(gòu)的基本思想是將所使用的數(shù)據(jù)與顯示和操 縱數(shù)據(jù)的用戶接口相分離。這個(gè)思想最初是在Smalltalk程序設(shè)計(jì)語言的環(huán)境中提出的。本節(jié)將簡(jiǎn)短描述 MVC的結(jié)構(gòu)和樣式,其關(guān)鍵思想已在微軟的文檔/視圖架構(gòu)和觀察者模式中使用。模型-視圖-控制者圖14.8表示的是使用 MVC構(gòu)

47、架表示的一個(gè)應(yīng)用的通用結(jié)構(gòu)。這里有一個(gè) 模型(model)視圖(view)對(duì)象,每個(gè)視對(duì)象,它存儲(chǔ)和維護(hù)所關(guān)注的數(shù)據(jù)。與這個(gè)模型相連接的是若干圖對(duì)象負(fù)責(zé)以一種特定方式顯示這些數(shù)據(jù)。例如,如果模型包含某些統(tǒng)計(jì)數(shù)據(jù), 不同視圖可以用棒圖或餅圖顯示這些數(shù)據(jù)。與這個(gè)模型相連接的還有若干控制者(controller)對(duì)象,控制者對(duì)象負(fù)責(zé)檢出用戶的輸入并把這些輸入轉(zhuǎn)遞給其它對(duì)象。這個(gè)構(gòu)架的三種構(gòu)件通過消息傳遞進(jìn)行通信。圖14.8表示了典型交互的細(xì)節(jié)。憑籍這些交互,該用戶的某個(gè)動(dòng)作將引起模型和與模型相連接的視圖的更新。圖14.18 MVC中的標(biāo)準(zhǔn)交互周期用戶的輸入是由特定的控制者檢出的,即在圖14.18中表

48、示的一組控制者中與用戶相連接的那個(gè)控制者檢出的。一般地,用戶的輸入可能改變模型的狀態(tài),該控制者向模型發(fā)送一個(gè)通知,將此改變告訴模型。 這個(gè)消息和交互的其他消息,典型地將把這些數(shù)據(jù)作為變量傳遞。為了清晰,圖14.18中沒有表示這些變量。當(dāng)模型接收到一個(gè)改變通知時(shí),該模型將發(fā)送一個(gè)更新消息給與它相連接的所有控制 者和視圖,這些控制者和視圖統(tǒng)稱為模型的依賴者。當(dāng)一個(gè)依賴者接收到一個(gè)更新消息時(shí), 它將詢問模型以取得模型狀態(tài)的最新信息,然后重新顯示所有受到影響的用戶接口部分。應(yīng)當(dāng)注意的是,一般說,控制者以及視圖都可能受到模型改變的影響。一個(gè)簡(jiǎn)單例子是菜單選項(xiàng)。這種構(gòu)架的好處在于它清楚地分離了處理應(yīng)用數(shù)據(jù)

49、的代碼和應(yīng)用的用戶接口。這就使 得容易開發(fā)新類型的視圖而不會(huì)影響到模型。這種方式在Smalltalk環(huán)境是特別重要的,Smalltalk是大量使用圖形用戶接口的開拓者之一。14. 9. 2文檔/視圖架構(gòu)文檔/視圖架構(gòu)是由微軟作為構(gòu)造支持圖形用戶接口的應(yīng)用的標(biāo)準(zhǔn)方法提出的。它可以看作是MVC構(gòu)架的簡(jiǎn)化,這里的文檔對(duì)應(yīng)于MVC中的模型,而視圖則是控制者和視圖兩種功能的組合。圖14.19表示的是文檔類和視圖類的結(jié)構(gòu)關(guān)系,以及引起它們進(jìn)行交互的某些操作。(ara I v n it viei/Listv->0nl4>1a! 圖14.19 文檔/視圖構(gòu)架(簡(jiǎn)化表示)圖14.20表示了文檔/視圖

50、構(gòu)架用于像鼠標(biāo)移動(dòng)這種用戶操作發(fā)生的典型交互的細(xì)節(jié)。注意,為了強(qiáng)調(diào)參與的對(duì)象是兩個(gè)用戶定義類CMy Document '和 CMyView '的實(shí)例,這個(gè)協(xié)作圖是用實(shí)例而不是用文檔和視圖角色對(duì)象畫出的。圖14.20典型的文檔/視圖交互文檔/視圖交互的總的結(jié)構(gòu)類似于圖14.18的MVC構(gòu)架。檢出用戶動(dòng)作的視圖發(fā)送通知消息“ UpdateAIIView ”到它的文檔。然后這個(gè)文檔又發(fā)送更新消息到所有鏈接到此文檔 的視圖,包括發(fā)送這個(gè)通知的視圖。這個(gè)更新消息進(jìn)而調(diào)用該視圖的OnDraw'方法,而在執(zhí)行這個(gè)方法的過程中,視圖在顯示與它相鏈接的文檔之前,典型地將檢索該文檔的當(dāng)前狀

51、態(tài)。14. 9. 3觀察者模式分離模型和視圖這個(gè)主題的另一個(gè)變體是四人組書中定義的觀察者模式。這個(gè)模式的意圖是“定義對(duì)象間的一種1對(duì)多的依賴,使得當(dāng)一個(gè)對(duì)象改變狀態(tài)時(shí),所有依賴于它的對(duì)象將被通知并相應(yīng)地更新”。圖14.21給出了表示了這個(gè)模式中涉及到的類的類圖。這個(gè)圖表示了到目前為止熟悉的一個(gè)模型和若干視圖之間的關(guān)系,在這個(gè)模式中模型稱為目標(biāo)(Subject),視圖現(xiàn)在稱為觀察者( Observer)。圖14.21觀察者模式觀察者模式意味著它可以應(yīng)用于比響應(yīng)用戶的動(dòng)作而去更新圖形顯示更廣泛的情況。因此,觀察者對(duì)象沒有必要負(fù)責(zé)測(cè)測(cè)用戶的輸入,使用觀察者所涉及的交互通常比用MVC或文檔/視圖簡(jiǎn)單。

52、不過如圖14.22所表示的,觀察者模式的交互基本結(jié)構(gòu)與早期考慮過的是 一樣的。一位未具體指明的客戶發(fā)送一個(gè)通知消息給某目標(biāo),然后目標(biāo)又發(fā)送更新消息到所有它的觀察者;這些觀察者又反過來可以查問該目標(biāo)的狀態(tài)。圖14.22觀察者模式中的交互14.10訪問者模式對(duì)庫存控制程序的應(yīng)用假定需要增加一個(gè)功能到庫存控制程序,以打印出一個(gè)組件中的所有零件和子組件。這種報(bào)告常稱為“零件剖析(parts explosion)”。一個(gè)簡(jiǎn)單方式是類似于已存在的"cost”函數(shù),可以增加一個(gè)“ explode(剖析)”函數(shù)到這個(gè)層次中。在零件類實(shí)現(xiàn)的這個(gè)函數(shù)實(shí)現(xiàn)將打 印出一個(gè)簡(jiǎn)單零件的細(xì)節(jié),而在組件類中它將通過

53、該組件的所有構(gòu)件調(diào)用每個(gè)構(gòu)件中的“ explode ”函數(shù)。這種方式可能存在的問題如下:1. 為了增加“ explode",該層次中的每個(gè)類都必須修改。如果層次太多,可能是不可行的,或者要付出非常昂貴的代價(jià)。2. 在“ cost”和“ explode”函數(shù)中控制通過組件的構(gòu)件的迭代的代碼是重復(fù)的。而在一個(gè)地方實(shí)現(xiàn)迭代以避免這種冗余更可取。3. “ part”類是這個(gè)應(yīng)用模型的一部分,這個(gè)方案讓part類直接負(fù)責(zé)產(chǎn)生輸出。然而在上一節(jié)討論的模式的基本原則是用戶接口代碼應(yīng)當(dāng)獨(dú)立于模型以容易修改和擴(kuò)展。這些問題可以用訪問者模式處理。訪問者模式的意圖是“根據(jù)操作作用的對(duì)象結(jié)構(gòu)元素,描述被執(zhí)行

54、操作;使得可以在不改變各元素的類的前提下定義作用于這些元素的新操 作?!币虼嗽L問者提供了一種方法,用這種方法實(shí)現(xiàn)的類在14.1節(jié)所討論的意義上,既是開放的同時(shí)又是封閉的。訪問者的工作原理是把表示數(shù)據(jù)的類,在此例中是零件層次,與可以應(yīng)用到該數(shù)據(jù)上的操作相分離。在新的“ visitors(訪問者)”層次中,操作是用類表示的。這兩個(gè)層次之間的 連接是通過在零件層次中定義一個(gè)單個(gè)操作“accepts a visitor"做出的。圖14.23表示的是,為了找出一個(gè)構(gòu)件價(jià)格,如何使用這種技術(shù)實(shí)現(xiàn)一個(gè)單個(gè)操作。lorn圖14.23使用Visitor找出構(gòu)件價(jià)格對(duì)于這個(gè)設(shè)計(jì),程序員為了找出一個(gè)構(gòu)件的

55、價(jià)格可以不再一般地調(diào)用該構(gòu)件類中的操作。代之的是,必須如下所述創(chuàng)建一個(gè)價(jià)格Visitor對(duì)象,并把此對(duì)象傳送給該構(gòu)件。CDBtVisitor visitor 三 new Co&tViBitaxjtub cost = compain旦工it 匚匸匸口匸j ;當(dāng)一個(gè)構(gòu)件接收到一個(gè)訪問者時(shí)會(huì)發(fā)生什么,依賴于運(yùn)行時(shí)該構(gòu)件的類型。如果該構(gòu)件是零件,確定該零件價(jià)格的任務(wù)就被委托給該訪問者對(duì)象中的函數(shù)。而為了使訪問者對(duì)象得到所需要的信息,也要把對(duì)這個(gè)零件的引用傳送給這個(gè)函數(shù)。如果該構(gòu)件是組件,則要執(zhí)行通過該組件的所有子組件迭代,并且每個(gè)子組件都要依次接收該訪問者。用這種方式,該訪問者對(duì)象被從一個(gè)構(gòu)件

56、傳送到一個(gè)構(gòu)件,直到在該組件中的每個(gè)零件都被訪問。確定該組件價(jià)格的實(shí)際工作是在該價(jià)格訪問者對(duì)象中進(jìn)行的。下面給出的這個(gè)類的定 義說明了這個(gè)總價(jià)格是如何隨著迭代的進(jìn)行而得出的。publ ic as Cosprivate ini. tota 1 】public void vis itPart p) total p.cos匚C);pixb lie void visit (Jls s<ainbLy a>/ nul 1 lrrplenient.3tiQn這段代碼說明,價(jià)格訪問者類包括確定一個(gè)構(gòu)件價(jià)格的所有代碼??墒沁@個(gè)遍歷零件層次的通用代碼是在該組件類的接收函數(shù)中出現(xiàn)的。為了確定組件的價(jià)格,這段代碼除了確保組件的所有構(gòu)件都被訪問之外,什么都沒有做,所以

溫馨提示

  • 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ì)自己和他人造成任何形式的傷害或損失。

最新文檔

評(píng)論

0/150

提交評(píng)論