第7章 設(shè)計(jì)原則_第1頁(yè)
第7章 設(shè)計(jì)原則_第2頁(yè)
第7章 設(shè)計(jì)原則_第3頁(yè)
第7章 設(shè)計(jì)原則_第4頁(yè)
第7章 設(shè)計(jì)原則_第5頁(yè)
已閱讀5頁(yè),還剩88頁(yè)未讀, 繼續(xù)免費(fèi)閱讀

下載本文檔

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

文檔簡(jiǎn)介

第7章設(shè)計(jì)原則《軟件體系結(jié)構(gòu)與設(shè)計(jì)實(shí)用教程》第二版7.1開閉原則7.1.1概念定義指軟件應(yīng)該對(duì)擴(kuò)展開放,對(duì)修改關(guān)閉。在設(shè)計(jì)一個(gè)模塊的時(shí)候,應(yīng)當(dāng)使這個(gè)模塊可以在不被修改源代碼的前提下被擴(kuò)展——改變這個(gè)模塊的行為。滿足“開-閉”原則的軟件系統(tǒng)通過(guò)擴(kuò)展已有的軟件系統(tǒng),可以提供新的行為,以滿足對(duì)軟件的新需求,使變化中的軟件系統(tǒng)有一定的適應(yīng)性和靈活性。同時(shí),己有的軟件模塊,特別是最重要的抽象層模塊不能再修改,這就使變化中的軟件系統(tǒng)有一定的穩(wěn)定性和延續(xù)性。這樣的軟件系統(tǒng)是一個(gè)在高層次上實(shí)現(xiàn)了復(fù)用的系統(tǒng),也是一個(gè)易于維護(hù)的系統(tǒng)。抽象化開閉原則的關(guān)鍵。對(duì)可變性封裝原則開閉原則還可以通過(guò)一個(gè)更加具體的“對(duì)可變性封裝原則”來(lái)描述。7.1.2實(shí)現(xiàn)方法抽象化運(yùn)用面向?qū)ο蠹夹g(shù),定義不再更改的抽象層,但允許有無(wú)窮無(wú)盡的行為在實(shí)現(xiàn)層被實(shí)現(xiàn)。面向?qū)ο笳Z(yǔ)言中抽象數(shù)據(jù)類型(例如,Java語(yǔ)言的抽象類或接口),可以規(guī)定出所有的具體類必須提供的方法的特征作為系統(tǒng)設(shè)計(jì)的抽象層;這個(gè)抽象層預(yù)見了所有的可能擴(kuò)展,在任何擴(kuò)展情況下都不改變;系統(tǒng)的抽象層不修改,滿足了開-閉原則的中對(duì)修改關(guān)閉的要求。同時(shí),由于從抽象層導(dǎo)出一個(gè)或多個(gè)新的具體類可以改變系統(tǒng)的行為,因此系統(tǒng)的設(shè)計(jì)對(duì)擴(kuò)展是開放的,這就滿足了開-閉原則對(duì)擴(kuò)展開放的要求。7.1.3實(shí)例絕對(duì)的對(duì)修改關(guān)閉是不可能的,設(shè)計(jì)人員必須對(duì)于他設(shè)計(jì)的模塊應(yīng)該對(duì)哪種變化封閉做出選擇。必須先猜測(cè)出最有可能發(fā)生的變化種類,然后構(gòu)造抽象來(lái)隔離那些變化,有時(shí)會(huì)把本該簡(jiǎn)單的設(shè)計(jì)做得非常復(fù)雜。但可以在發(fā)生小變化時(shí),就及早去想辦法應(yīng)對(duì)發(fā)生更大變化的可能。在最初編寫代碼時(shí),假設(shè)變化不會(huì)發(fā)生。當(dāng)變化發(fā)生時(shí),就創(chuàng)建抽象來(lái)隔離以后發(fā)生的同類變化。實(shí)例某圖形界面系統(tǒng)提供了各種不同形狀的按鈕,客戶端代碼可針對(duì)這些按鈕進(jìn)行編程,用戶可能會(huì)改變需求要求使用不同的按鈕,原始設(shè)計(jì)方案如圖所示?,F(xiàn)對(duì)該系統(tǒng)進(jìn)行重構(gòu),使之滿足開閉原則的要求,如圖所示。7.2里氏代換原則7.2.1概念里氏代換原則由麻省理工學(xué)院教授BarbaraLiskov(2008年圖靈獎(jiǎng)得主、美國(guó)第一位計(jì)算機(jī)科學(xué)女博士)和卡內(nèi)基.梅隆大學(xué)JeannetteWing(周以真)教授于1994年提出。里氏代換原則(LiskovSubstitutionPrinciple,LSP)是指所有引用基類(父類)的地方必須能透明地使用其子類的對(duì)象。子類型必須能夠替換掉它們的父類型;子類繼承了父類,子類可以以父類的身份出現(xiàn)。也就是說(shuō),在軟件里面,把父類都替換成它的子類,程序的行為沒有變化。里氏代換原則的一種描述是:如果對(duì)每一個(gè)類型為T1的對(duì)象o1,都有類型為T2的對(duì)象o2,使得以T1定義的所有程序P在所有的對(duì)象o1都代換成o2時(shí),程序P的行為沒有變化,那么類型T2是類型T1的子類型。假設(shè)有兩個(gè)類,一個(gè)是Base類,另一個(gè)是Sub類,并且Sub類是Base類的子類。那么一個(gè)方法如果可以接受一個(gè)父類對(duì)象b的話:methodl(Baseb),那么它必然可以接受一個(gè)子類對(duì)象s,即method1(Subs)是正確的。注意,反過(guò)來(lái)的代換則不成立,即如果一個(gè)軟件實(shí)體使用的是一個(gè)子類的話,那么它不一定適用于父類。如果一個(gè)方法method2接受子類對(duì)象為參數(shù)的話:method2(Subs),那么method2(Baseb)不一定正確。里氏代換原則分析classAnimal{//……}classDogextendsAnimal{eat(){}//……}classCatextendsAnimal{eat(){}//……}classSheepextendsAnimal{eat(){}//……}classclient{Animalanimal=newCat();//Animalanimal=newDog();animal.eat();//……}7.2.2Java語(yǔ)言與里氏代換原則在編譯時(shí)期,Java語(yǔ)言編譯器會(huì)檢查一個(gè)程序是否符合里氏代換原則。例如,一個(gè)父類Base聲明了一個(gè)public方法method(),那么其子類Sub不能將這個(gè)方法的訪問權(quán)限從public改換成為package/private。也就是說(shuō),子類型不能使用一個(gè)低訪問權(quán)限的方法privatemethod()覆蓋父類的方法publicmethod()呢。原因是客戶端完全有可能調(diào)用父類的公開方法。若以子類代之,這個(gè)方法變成了私有的,客戶端不能調(diào)用。這是違反里氏代換法則的,Java編譯器編譯會(huì)報(bào)錯(cuò)。但是,Java編譯器不能檢查一個(gè)系統(tǒng)在具體實(shí)現(xiàn)和商業(yè)邏輯上是否滿足里氏代換法則。典型的例子就是“圓形類是否是橢圓形類的子類”和“正方形類是否是長(zhǎng)方形類的子類”的問題。7.2.3實(shí)例1.取消繼承關(guān)系如果有兩個(gè)具有繼承關(guān)系的類A和B違反了里氏代換原則,就要取消繼承關(guān)系,可考慮采用下面的方案之一。(1)創(chuàng)建一個(gè)新的抽象類C,作為兩個(gè)具體類的父類,將A和B的共同行為移動(dòng)到C中,從而解決A和B行為不完全一致的問題。如圖所示。(圓形是否橢圓形子類的問題)(2)從A和B的繼承關(guān)系改寫為合成/聚合關(guān)系——合成/聚合復(fù)用原則2.CRM系統(tǒng)在CRM系統(tǒng)中客戶(Customer)可以分為VIP客戶(VIPCustomer)和普通客戶(CommonCustomer)兩類系統(tǒng)需要提供一個(gè)發(fā)送Email的功能原始設(shè)計(jì)方案如圖所示CRM系統(tǒng)CRM系統(tǒng)在本實(shí)例中,可以考慮增加一個(gè)新的抽象客戶類Customer,而將CommonCustomer和VIPCustomer類作為其子類。郵件發(fā)送類EmailSender類針對(duì)抽象客戶類Customer編程。根據(jù)里氏代換原則,能夠接受基類對(duì)象的地方必然能夠接受子類對(duì)象,因此將EmailSender中的send()方法的參數(shù)類型改為Customer,如果需要增加新類型的客戶,只需將其作為Customer類的子類即可。重構(gòu)后的結(jié)構(gòu)如圖所示CRM系統(tǒng)7.3依賴倒轉(zhuǎn)原則7.3.1倒轉(zhuǎn)的含義每一個(gè)邏輯的實(shí)現(xiàn)都是由原子邏輯組成的不可分割的原子邏輯就是低層模塊原子邏輯的再組裝就是高層模塊。傳統(tǒng)的設(shè)計(jì)高層模塊依賴低層模塊在傳統(tǒng)的設(shè)計(jì)中,復(fù)用側(cè)重于具體層次模塊的復(fù)用比如算法的復(fù)用、數(shù)據(jù)結(jié)構(gòu)的復(fù)用、函數(shù)庫(kù)的復(fù)用等,都是具體層次模塊里的復(fù)用。面向過(guò)程的開發(fā)時(shí),為使常用代碼可復(fù)用,一般都會(huì)把這些常用代碼寫成許多函數(shù)的程序庫(kù),這樣做新項(xiàng)目時(shí),去調(diào)用這些低層的函數(shù)就可以了。比如項(xiàng)目大多要訪問數(shù)據(jù)庫(kù),所以就把訪問數(shù)據(jù)庫(kù)的代碼寫成了函數(shù),每次做新項(xiàng)目時(shí)就去調(diào)用這些函數(shù)修改較高層次的結(jié)構(gòu)依賴于較低層次的結(jié)構(gòu),較低層次的結(jié)構(gòu)又進(jìn)一步依賴于更低層次的結(jié)構(gòu),如此繼續(xù),直到依賴于每一行的代碼。較低層次上的修改,會(huì)造成較高層次的修改,直到高層次邏輯的修改??删S護(hù)性傳統(tǒng)的做法也強(qiáng)調(diào)具體層次上的可維護(hù)性,包括一個(gè)函數(shù)、數(shù)據(jù)結(jié)構(gòu)等的可維護(hù)性,而不是高級(jí)層次上的可維護(hù)性。依賴于抽象而如果不管高層模塊還是低層模塊,它們都依賴于抽象,具體一點(diǎn)就是接口或抽象類,只要接口是穩(wěn)定的,那么任何一個(gè)的更改都不用擔(dān)心其他受到影響,這就使得無(wú)論高層模塊還是低層模塊都可以很容易地被復(fù)用。1.依賴倒轉(zhuǎn)原則定義抽象不應(yīng)當(dāng)依賴于細(xì)節(jié);細(xì)節(jié)應(yīng)當(dāng)依賴于抽象。要針對(duì)接口編程,不要針對(duì)實(shí)現(xiàn)編程。在程序設(shè)計(jì)語(yǔ)言中抽象就是指接口或抽象類,兩者都是不能直接被實(shí)例化的細(xì)節(jié)就是實(shí)現(xiàn)類,實(shí)現(xiàn)接口或繼承抽象類而產(chǎn)生的類就是細(xì)節(jié),其特點(diǎn)就是可以直接被實(shí)例化,也就是可以加上一個(gè)關(guān)鍵字new產(chǎn)生一個(gè)對(duì)象。7.3.2概念代碼要依賴于抽象的類,而不要依賴于具體的類;要針對(duì)接口或抽象類編程,而不是針對(duì)具體類編程。針對(duì)接口編程是指應(yīng)當(dāng)使用抽象類或Java接口進(jìn)行變量的類型聲明、參量的類型聲明、方法的返還類型聲明,以及數(shù)據(jù)類型的轉(zhuǎn)換等。不要針對(duì)實(shí)現(xiàn)編程的意思是指不應(yīng)使用具體類進(jìn)行這些工作要達(dá)到這個(gè)要求,一個(gè)具體類應(yīng)當(dāng)只實(shí)現(xiàn)抽象類或Java接口中聲明過(guò)的方法,而不應(yīng)給出多余方法依賴倒轉(zhuǎn)原則在程序設(shè)計(jì)語(yǔ)言中的表現(xiàn)模塊間的依賴是通過(guò)抽象發(fā)生,實(shí)現(xiàn)類之間不發(fā)生直接的依賴關(guān)系,其依賴關(guān)系是通過(guò)接口或抽象類產(chǎn)生的;接口或抽象類不依賴于實(shí)現(xiàn)類;實(shí)現(xiàn)類依賴接口或抽象類。只要一個(gè)被引用的對(duì)象存在抽象類型,就應(yīng)當(dāng)在任何引用此對(duì)象的地方使用抽象類型參量的類型聲明方法返還類型的聲明屬性變量的類型聲明等……Java中的上轉(zhuǎn)型在JavaAPI中定義了AbstractList類,它繼承了AbstractCollection類,實(shí)現(xiàn)了List接口,并有AbstractSequentialList,ArrayList,Vector等直接子類對(duì)于代碼:Listemployees=newVector();在employees對(duì)象聲明的類型是List接口,但卻實(shí)例化為Vector類的對(duì)象。這就是針對(duì)接口編程的含義。這句其實(shí)是指父類對(duì)象employees是子類Vector對(duì)象的上轉(zhuǎn)型對(duì)象。盡量不要使用下面的聲明語(yǔ)句:Vectoremployees=newVector();二者的區(qū)別在于前者使用一個(gè)抽象類型(List是Java接口)作為類型,而后者使用一個(gè)具體類(Vector)作為變量的類型。前者的優(yōu)點(diǎn)是在將Vector類型轉(zhuǎn)換成ArrayList時(shí),需要改動(dòng)得很少:Listemployees=newArrayList();這樣的程序具有更好的靈活性,因?yàn)槌苏{(diào)用構(gòu)造函數(shù)的部分之外,程序的其余部分沒有變化。類之間的耦合零耦合關(guān)系:兩個(gè)類沒有耦合關(guān)系具體耦合關(guān)系:發(fā)生在兩個(gè)具體的(可實(shí)例化的)類之間,經(jīng)由一個(gè)類對(duì)另一個(gè)具體類的直接引用造成

抽象耦合關(guān)系:發(fā)生在一個(gè)具體類和一個(gè)抽象類(或者Java接口)之間,使兩個(gè)必須發(fā)生關(guān)系的類之間存有最大的靈活性依賴倒轉(zhuǎn)原則要求客戶端依賴于抽象耦合,以抽象方式耦合是依賴倒轉(zhuǎn)原則的關(guān)鍵。2.抽象耦合3.依賴倒轉(zhuǎn)原則的特點(diǎn)依賴倒轉(zhuǎn)原則作用減少類間的耦合性提高系統(tǒng)的可維護(hù)性性減少并行開發(fā)引起的風(fēng)險(xiǎn)提高代碼的可讀性依賴倒轉(zhuǎn)原則的缺點(diǎn)但是,依賴倒轉(zhuǎn)原則是最不容易實(shí)現(xiàn)的。為滿足依賴倒轉(zhuǎn)原則,對(duì)象的創(chuàng)建一般要使用對(duì)象工廠,以避免對(duì)具體類的直接引用。同時(shí),依賴倒轉(zhuǎn)原則還會(huì)導(dǎo)致大量的類。在抽象層次上的耦合雖然有靈活性,但也帶來(lái)了額外的復(fù)雜性。在某些情況下,如果一個(gè)具體類發(fā)生變化的可能性非常小,那么抽象耦合能發(fā)揮的好處便十分有限,這時(shí)使用具體耦合反而會(huì)更好。7.3.3實(shí)例賬號(hào)管理賬號(hào)Account類中創(chuàng)建了一個(gè)表示賬號(hào)種類的AccountType類的對(duì)象,它們是聚合關(guān)系。AccountType類是抽象類,有兩個(gè)的具體子類。代表儲(chǔ)蓄賬號(hào)的Saving類代表結(jié)算賬號(hào)的Settlement類抽象類AccountType定義出所有具體子類必須實(shí)現(xiàn)的接口。abstractpublicclassAccountType{publicabstractvoiddeposit(floatamt);}AccountType有兩個(gè)子類型:1)儲(chǔ)蓄賬號(hào):Savings具體類2)結(jié)算賬號(hào):Settlement具體類下面是一個(gè)具體的AccountType的子類,它實(shí)現(xiàn)了抽象超類AccountType所聲明的接口。publicclassSavingsextendsAccountType{publicvoiddeposit(floatamt){//writeyourcodehere}}Saving類代表儲(chǔ)蓄賬號(hào),AccountType子類。Settlement是AccountType的另一個(gè)具體子類,它也實(shí)現(xiàn)了抽象超類AccountType所聲明的接口。publicclassSettlementextendsAccountType{publicvoiddeposit(floatamt){//writeyourcodehere}}Settlement代表支票賬號(hào),AccountType子類。Account類源代碼如下所示。publicclassAccount{privateAccountTypeaccountType;publicAccount(AccountTypeacctType){accountType=acctType;//writeyourcodehere}publicvoiddeposit(floatamt){accountType.deposite(amt);//writeyourcodehere}}在這個(gè)例子里,Account類依賴于AccountType抽象類型,而不是它的子類型。Account類有聚合關(guān)系A(chǔ)ccountType,Account類有AccountType對(duì)象的引用AccountType類是抽象類型。每一個(gè)抽象類型均有多于一個(gè)的具體實(shí)現(xiàn)。AccountType有Saving和Settlement兩種具體子類Account類并不依賴于具體類,因此當(dāng)有新的具體類型添加到系統(tǒng)中時(shí),Account類不必改變。例如,如果系統(tǒng)引進(jìn)了一種新型的賬號(hào):代表支票賬戶的Checking類,Account類以及系統(tǒng)里面所有其他的依賴于AccountType抽象類的客戶端類均不必改變。Checking的源代碼如下所示。publicclassCheckingextendsAccountType{publicvoiddeposit(floatamt){//writeyourcodehere}}7.4合成/聚合復(fù)用原則合成復(fù)用原則(CompositeReusePrinciple,CRP)又稱為組合/聚合復(fù)用原則(Composition/AggregateReusePrinciple,CARP)。在一個(gè)新的對(duì)象里面使用一些已有的對(duì)象,使之成為新對(duì)象的一部分;新的對(duì)象可以調(diào)用己有對(duì)象的功能,從而達(dá)到復(fù)用己有功能的目的。盡量使用對(duì)象組合,而不是繼承來(lái)達(dá)到復(fù)用的目的。7.4.1概念7.4.2合成/聚合復(fù)用與繼承復(fù)用1.繼承復(fù)用繼承復(fù)用中,父類具有子類共同的屬性和方法,而子類通過(guò)增加新的屬性和方法來(lái)擴(kuò)展父類的實(shí)現(xiàn)。繼承復(fù)用的主要優(yōu)點(diǎn)是:父類的大部分功能可以通過(guò)繼承關(guān)系自動(dòng)進(jìn)入子類,所以新的實(shí)現(xiàn)比較容易;修改或擴(kuò)展繼承而來(lái)的實(shí)現(xiàn)也比較容易?!獙?shí)現(xiàn)簡(jiǎn)單,易于擴(kuò)展剛開始學(xué)會(huì)用面向?qū)ο蟮睦^承時(shí),感覺它既新穎又功能強(qiáng)大,所以只要可以用,就都用上繼承。這就好比是“有了新錘子,所有的東西看上去都成了釘子”。但事實(shí)上,很多情況用繼承會(huì)帶來(lái)麻煩?!荒茉谟邢薜沫h(huán)境中使用繼承復(fù)用缺點(diǎn)對(duì)象的繼承關(guān)系是在編譯時(shí)就定義好了,所以無(wú)法在運(yùn)行時(shí)改變從父類繼承的實(shí)現(xiàn)?!獜幕惱^承而來(lái)的實(shí)現(xiàn)是靜態(tài)的,不可能在運(yùn)行時(shí)發(fā)生改變,沒有足夠的靈活性“白箱”復(fù)用,父類的內(nèi)部細(xì)節(jié)對(duì)子類是透明的,繼承將父類的實(shí)現(xiàn)細(xì)節(jié)暴露給子類,繼承復(fù)用破壞包裝,破壞系統(tǒng)的封裝性。子類的實(shí)現(xiàn)與它的父類有非常緊密的依賴關(guān)系,以至于父類實(shí)現(xiàn)中的任何變化必然會(huì)導(dǎo)致子類發(fā)生變化,而且一級(jí)又一級(jí)的子類都要發(fā)生改變。當(dāng)你想要復(fù)用子類時(shí),如果繼承下來(lái)的實(shí)現(xiàn)不適合解決新的問題,則父類必須重寫或被其他更適合的類替換。這種依賴關(guān)系限制了靈活性,并最終限制了復(fù)用性。合成或聚合將已有的對(duì)象納入到新對(duì)象中,使之成為新對(duì)象的一部分,因此新的對(duì)象可以調(diào)用已有對(duì)象的功能。(Has-A)優(yōu)點(diǎn)耦合度相對(duì)較低,選擇性地調(diào)用成員對(duì)象的操作。新對(duì)象訪問已有對(duì)象的惟一方法是通過(guò)已有對(duì)象的接口;可以在運(yùn)行時(shí)動(dòng)態(tài)進(jìn)行;“黑箱”復(fù)用,已有對(duì)象的內(nèi)部細(xì)節(jié)對(duì)新對(duì)象不可見;合成/聚合作為復(fù)用手段可以應(yīng)用到幾乎任何環(huán)境中去。缺點(diǎn)通過(guò)使用這種復(fù)用建造的系統(tǒng)會(huì)有較多的對(duì)象需要管理2.合成/聚合復(fù)用組合/聚合可以使系統(tǒng)更加靈活,類與類之間的耦合度降低,一個(gè)類的變化對(duì)其他類造成的影響相對(duì)較少,因此一般首選使用組合/聚合來(lái)實(shí)現(xiàn)復(fù)用;其次才考慮繼承,在使用繼承時(shí),需要嚴(yán)格遵循里氏代換原則,有效使用繼承會(huì)有助于對(duì)問題的理解,降低復(fù)雜度,而濫用繼承反而會(huì)增加系統(tǒng)構(gòu)建和維護(hù)的難度以及系統(tǒng)的復(fù)雜度,因此需要慎重使用繼承復(fù)用。3.Has-A與Is-AIs-A是嚴(yán)格的分類學(xué)意義上的定義,意思是一個(gè)類是另一個(gè)類的一種。Has-A表示某一個(gè)角色具有某一項(xiàng)責(zé)任;代表一個(gè)類是另一個(gè)類的一個(gè)角色,而不是另一個(gè)類的一個(gè)特殊種類。繼承復(fù)用是Is-A,合成/聚合復(fù)用是Has-A。根據(jù)里氏代換原則,如果兩個(gè)類的關(guān)系是“Has-A”關(guān)系而不是“Is-A”關(guān)系,這兩個(gè)類一定違反里氏代換原則;只有兩個(gè)類滿足里氏代換原則,才能是“Is-A”關(guān)系。7.4.3實(shí)例要正確地使用繼承關(guān)系,必須透徹地理解里氏代換原則。如果有兩個(gè)具有繼承關(guān)系的類違反了里氏代換原則,就要取消繼承關(guān)系??梢詣?chuàng)建一個(gè)新的共同的抽象父類,取消繼承關(guān)系,這一辦法已經(jīng)在“里氏代換原則”中討論過(guò)(圓與橢圓、正方形與長(zhǎng)方形);還可以將繼承關(guān)系改寫為合成/聚合關(guān)系。如果有一個(gè)代表人的父類People,它有三個(gè)子類,分別是代表雇員的Employee類、代表經(jīng)理的Manager類和代表學(xué)生的Student類。Java語(yǔ)言中,這種繼承關(guān)系是不正確的。因?yàn)镋mployee、Manager和Student分別描述一種角色——雇員、經(jīng)理和學(xué)生,而人可以同時(shí)有幾種不同的角色。例如,一個(gè)經(jīng)理可以同時(shí)在讀在職研究生,也是一個(gè)學(xué)生。Java語(yǔ)言中,只支持單重繼承。使用繼承來(lái)實(shí)現(xiàn)角色,會(huì)使一個(gè)人只能具有一種角色。一個(gè)人在成為雇員身份后,就不能成為經(jīng)理或?qū)W生了,這是不合理的。這就是說(shuō),當(dāng)一個(gè)類是另一個(gè)類的角色時(shí),不應(yīng)當(dāng)使用繼承描述這種關(guān)系。classPeople{//……}classEmployeeextendsPeople{//……}classManagerextendsPeople{//……}classStudentextendsPeople{//……}這一錯(cuò)誤的設(shè)計(jì)源自于把角色的等級(jí)結(jié)構(gòu)與人的等級(jí)結(jié)構(gòu)混淆起來(lái),把Has-A角色誤解為Is-A角色。使用繼承來(lái)實(shí)現(xiàn)角色,只能使每一個(gè)人具有Has-A角色,而且繼承是靜態(tài)的,造成一個(gè)人在成為雇員身份后,就永遠(yuǎn)為雇員,不能稱為經(jīng)理或?qū)W生。糾正這一錯(cuò)誤的關(guān)鍵是區(qū)分人與角色的區(qū)別。所以,People類和Employee類、Manager類、Student類之間不是繼承關(guān)系,這里采用將繼承關(guān)系改寫為合成/聚合關(guān)系的方法解決這個(gè)問題。增加一個(gè)類Role表示角色,People類和Role類之間是合成關(guān)系,Role類和Employee類、Manager類、Student類之間是繼承關(guān)系。這樣,每一個(gè)人都可以有一個(gè)以上的角色,他可以同時(shí)是經(jīng)理,又是學(xué)生。而且由于人與角色是合成關(guān)系,所以角色可以動(dòng)態(tài)變化。一個(gè)人可以開始是一個(gè)雇員,然后晉升為經(jīng)理;然后他又在職讀研究生,又成為了學(xué)生。classPeople{Roler=newManager();//……}classRole{//……}classEmployeeextendsRole{//……}classManagerextendsRole{//……}classStudentextendsRole{//……}7.5迪米特法則迪米特法則來(lái)自于1987年秋美國(guó)東北大學(xué)(NortheasternUniversity)一個(gè)名為“Demeter”的研究項(xiàng)目。迪米特法則(LawofDemeter,LoD)又稱為最少知識(shí)原則(LeastKnowledgePrinciple,LKP),是指一個(gè)對(duì)象應(yīng)當(dāng)對(duì)其他對(duì)象有盡可能少的了解。有多種定義方法,其中幾種典型定義如下:(1)不要和“陌生人”說(shuō)話。(2)只與你的直接朋友通信。(3)每一個(gè)軟件單位對(duì)其他的單位都只有最少的知識(shí),而且局限于那些與本單位密切相關(guān)的軟件單位。7.5.1概念在迪米特法則中,對(duì)于一個(gè)對(duì)象,其朋友包括以下幾類:(1)當(dāng)前對(duì)象本身(this);(2)以參數(shù)形式傳入到當(dāng)前對(duì)象方法中的對(duì)象;(3)當(dāng)前對(duì)象的成員對(duì)象;(4)如果當(dāng)前對(duì)象的成員對(duì)象是一個(gè)集合,那么集合中的元素也都是朋友;(5)當(dāng)前對(duì)象所創(chuàng)建的對(duì)象。任何一個(gè)對(duì)象,如果滿足上面的條件之一,就是當(dāng)前對(duì)象的“朋友”,否則就是“陌生人”。對(duì)象的朋友與陌生人簡(jiǎn)單地說(shuō),迪米特法則就是指一個(gè)軟件實(shí)體應(yīng)當(dāng)盡可能少的與其他實(shí)體發(fā)生相互作用。這樣,當(dāng)一個(gè)模塊修改時(shí),就會(huì)盡量少的影響其他的模塊,擴(kuò)展會(huì)相對(duì)容易,這是對(duì)軟件實(shí)體之間通信的限制,它要求限制軟件實(shí)體之間通信的寬度和深度。迪米特法則分析迪米特法則可分為狹義法則廣義法則如果兩個(gè)類之間不必彼此直接通信,那么這兩個(gè)類就不應(yīng)當(dāng)發(fā)生直接的相互作用這時(shí),如果其中的一個(gè)類需要調(diào)用另一個(gè)類(陌生人)的某一個(gè)方法,可通過(guò)第三者(朋友)轉(zhuǎn)發(fā)這個(gè)調(diào)用。但這時(shí),第三者需要額外增加相關(guān)的方法。1.狹義迪米特法則特點(diǎn)(1)可以降低類之間的耦合但是會(huì)在系統(tǒng)中增加大量的小方法并散落在系統(tǒng)的各個(gè)角落。這些方法僅僅是傳遞間接的調(diào)用,因此與系統(tǒng)的商務(wù)邏輯無(wú)關(guān)。當(dāng)設(shè)計(jì)師試圖從一張類圖看出總體的架構(gòu)時(shí),這些小的方法會(huì)造成迷惑和困擾。特點(diǎn)(2)可以使一個(gè)系統(tǒng)的局部設(shè)計(jì)簡(jiǎn)化,因?yàn)槊恳粋€(gè)局部都不會(huì)和遠(yuǎn)距離的對(duì)象有直接的關(guān)聯(lián)。但是也會(huì)造成系統(tǒng)的不同模塊之間的通信效率降低,使得系統(tǒng)的不同模塊之間不容易協(xié)調(diào)。狹義迪米特法則特點(diǎn)指對(duì)對(duì)象之間的信息流量、流向以及信息的影響的控制,主要是對(duì)信息隱藏的控制。信息的隱藏可以使各個(gè)子系統(tǒng)之間脫耦,從而允許它們獨(dú)立地被開發(fā)、優(yōu)化、使用和修改。這種脫耦化可以有效地加快系統(tǒng)的開發(fā)過(guò)程,因?yàn)榭梢元?dú)立地同時(shí)開發(fā)各個(gè)模塊。它可以使維護(hù)過(guò)程變得容易,因?yàn)樗械哪K都容易讀懂,特別是不必?fù)?dān)心對(duì)其他模塊的影響。同時(shí)可以促進(jìn)軟件的復(fù)用,由于每一個(gè)模塊都不依賴于其他模塊而存在,因此每一個(gè)模塊都可以獨(dú)立地在其他的地方使用。一個(gè)系統(tǒng)的規(guī)模越大,信息的隱藏就越重要,而信息隱藏的重要性也就越明顯。2.廣義迪米特法則封裝在軟件系統(tǒng)中,一個(gè)模塊設(shè)計(jì)得好不好的最主要、最重要的標(biāo)志,就是該模塊在多大的程度上將自己的內(nèi)部數(shù)據(jù)和其他與實(shí)現(xiàn)有關(guān)的細(xì)節(jié)隱藏起來(lái)。信息的隱藏(封裝)軟件設(shè)計(jì)的基本教義之一一個(gè)設(shè)計(jì)得好的模塊可以將它所有的實(shí)現(xiàn)細(xì)節(jié)隱藏起來(lái),徹底地將提供給外界的API和自己的實(shí)現(xiàn)分隔開來(lái)。這樣一來(lái),模塊與模塊之間就可以僅僅通過(guò)彼此的API相互通信,而不理會(huì)模塊內(nèi)部的工作細(xì)節(jié)。迪米特法則的主要用途控制信息的過(guò)載迪米特法則的核心觀念類間解耦,這樣類的復(fù)用性就可以提高在對(duì)其他類的引用上,一個(gè)對(duì)象對(duì)其他對(duì)象的引用應(yīng)當(dāng)降到最低。迪米特法則分析7.5.2實(shí)例明星經(jīng)紀(jì)人是明星與外界的橋梁。外界可以是各影視公司,需要明星代言的商家等。經(jīng)紀(jì)人參與活動(dòng)與演出的洽淡,歌迷粉絲以及各種事情的代辦與處理等。也就是說(shuō)明星并不需要與外界直接發(fā)生相互作用,而是明星經(jīng)紀(jì)人與外界發(fā)生直接的相互作用。這樣就是符合迪米特法則的。不滿足迪米特法則的系統(tǒng)系統(tǒng)由三個(gè)類組成,分別是代表明星的Star類,代表明星代理人的Agent類和代表外界的Outside_world類。代表明星代理人的Agent類和代表外界的Outside_world類直接發(fā)生相互作用,所以Agent類中有一個(gè)Outside_world類的對(duì)象,Agent類的provide()方法提供了自己所創(chuàng)建的Outside_world類的實(shí)例。classAgent{privateOutside_worldow=newOutside_world();publicOutside_worldprovide(){returnow;}}Star類具有一個(gè)方法operation1(),這個(gè)方法的參數(shù)是Agent類的對(duì)象,代表明星的Star類和代表明星代理人的Agent類直接發(fā)生相互作用。classStar{publicvoidoperation1(Agentagent){Outside_worldow=vide();ow.operation2();}}classOutside_world{publicvoidoperation2(){//……}}注意,Star類的方法operation1()不滿足迪米特法則。因?yàn)檫@個(gè)方法引用了Outside_world類的對(duì)象,而代表明星的Star類是不應(yīng)該與代表外界的Outside_world類直接發(fā)生相互作用的。使用迪米特法則進(jìn)行改造可以使用迪米特法則進(jìn)行改造,使代表明星的Star類是不與代表外界的Outside_world類直接發(fā)生相互作用。改造的做法就是調(diào)用轉(zhuǎn)發(fā),Star不需知道Outside_world的存在就可以做同樣的事情。classStar{publicvoidoperation1(Agentagent){agent.forward();}}Star類通過(guò)調(diào)用Agent類的forward()方法,做到了原來(lái)需要Star類調(diào)用Outside_world類的對(duì)象才能夠做到的事情。classAgent{privateOutside_worldow=newOutside_world();publicvoidforward(){ow.operation2();}}Agent類的forward()方法所做的就是以前Star類要做的事,使用了Outside_world類的operation2()方法,而這種forward()方法就叫做轉(zhuǎn)發(fā)方法。由于使用了調(diào)用轉(zhuǎn)發(fā),使得調(diào)用的具體細(xì)節(jié)被隱藏在Agent類的內(nèi)部,從而使Star類與Outside_world類之間的直接聯(lián)系被省略掉了。這樣一來(lái),使得系統(tǒng)內(nèi)部的耦合度降低。在系統(tǒng)的某一個(gè)類需要修改時(shí),僅僅會(huì)影響到這個(gè)類的直接相關(guān)類,而不會(huì)影響到其余部分。7.6單一職責(zé)原則

問題由來(lái)類T負(fù)責(zé)兩個(gè)不同的職責(zé):職責(zé)P1,職責(zé)P2。當(dāng)由于職責(zé)P1需求發(fā)生改變而需要修改類T時(shí),有可能會(huì)導(dǎo)致原本運(yùn)行正常的職責(zé)P2功能發(fā)生故障。解決方案遵循單一職責(zé)原則(SingleResponsibilityPrinciple,SRP)

分別建立兩個(gè)類T1、T2,使T1完成職責(zé)P1功能,T2完成職責(zé)P2功能。這樣,當(dāng)修改類T1時(shí),不會(huì)使職責(zé)P2發(fā)生故障風(fēng)險(xiǎn);同理,當(dāng)修改T2時(shí),也不會(huì)使職責(zé)P1發(fā)生故障風(fēng)險(xiǎn)。定義一個(gè)對(duì)象應(yīng)該只包含單一的職責(zé),并且該職責(zé)被完整地封裝在一個(gè)類中——功能要單一。就一個(gè)類而言,應(yīng)該僅有一個(gè)引起它變化的原因。7.6.1概念某基于Java的C/S系統(tǒng)的“登錄功能”通過(guò)如下登錄類(Login)實(shí)現(xiàn):7.6.2實(shí)例使用單一職責(zé)原則進(jìn)行重構(gòu)7.7接口隔離原則接口隔離原則(InterfaceSegregationPrinciple,ISP)

定義客戶端不應(yīng)該依賴那些它不需要的接口。一旦一個(gè)接口太大,則需要將它分割成一些更細(xì)小的接口,使用該接口的客戶端僅需知道與之相關(guān)的方法即可。7.7.1概念從代碼重構(gòu)的角度討論怎樣將一個(gè)臃腫的角色重新分割成更為合適的較小角色。(1)全文查詢引擎的系統(tǒng)設(shè)計(jì)以一個(gè)網(wǎng)站的全文查詢引擎的系統(tǒng)設(shè)計(jì)為例。一個(gè)動(dòng)態(tài)的資料網(wǎng)站將大量的文件資料存儲(chǔ)在文件中或關(guān)系數(shù)據(jù)庫(kù)里面,用戶可以通過(guò)輸入一個(gè)和數(shù)個(gè)關(guān)鍵詞進(jìn)行全網(wǎng)站的全文搜索。這個(gè)搜索引擎需要維持一個(gè)索引庫(kù),在本例子里面索引庫(kù)以文本文件方式存于文件系統(tǒng)中。源數(shù)據(jù)被修改、刪除或增加時(shí),搜索引擎要做相應(yīng)的動(dòng)作,以保證引擎的索引文件也被相應(yīng)地更新。7.7.2實(shí)例(2)反面例子首先,下圖所示為一個(gè)不好的解決方案。一個(gè)接口負(fù)責(zé)所有的操作,從提供搜索功能到建立索引的功能,甚至包括搜索結(jié)果集合的功能均在一個(gè)接口內(nèi)提供。這個(gè)解決方案違反了角色分割原則,把不同功能的接口放在一起,由一個(gè)接口給出包括搜索器角色、索引生成器角色以及搜索結(jié)果集角色在內(nèi)的所有角色。interfacesearchEngine{voidsearch(String[]keyword);voidgetResultset();voidreIndexAll();voidupdateIndex();voidfirst();voidlast();voidnext();voidprevious();StringgetExcerpt(); StringgetFullRecord();}(3)角色的分割那么,遵守接口隔離原則的做法是怎樣的呢?如圖所示??梢钥闯?,搜

溫馨提示

  • 1. 本站所有資源如無(wú)特殊說(shuō)明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請(qǐng)下載最新的WinRAR軟件解壓。
  • 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請(qǐng)聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
  • 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁(yè)內(nèi)容里面會(huì)有圖紙預(yù)覽,若沒有圖紙預(yù)覽就沒有圖紙。
  • 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
  • 5. 人人文庫(kù)網(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)論