面向?qū)ο缶幊淘瓌t--11.ppt_第1頁
面向?qū)ο缶幊淘瓌t--11.ppt_第2頁
面向?qū)ο缶幊淘瓌t--11.ppt_第3頁
面向?qū)ο缶幊淘瓌t--11.ppt_第4頁
面向?qū)ο缶幊淘瓌t--11.ppt_第5頁
已閱讀5頁,還剩39頁未讀, 繼續(xù)免費閱讀

下載本文檔

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

文檔簡介

設(shè)計模式(1),導(dǎo)言:面向?qū)ο笤O(shè)計原則,目錄,1 面向?qū)ο蟮脑O(shè)計原則 2 設(shè)計模式概論 3 單件 4 觀察者,面向?qū)ο蟮脑O(shè)計原則,1單一職責(zé)SRP 2.OCP開閉原則 3.里氏代換LSP 4.依賴倒轉(zhuǎn)DIP 5.接口隔離ISP 6.迪米特法則LOD 7合成聚合復(fù)用原則(CARP),Booch和Rumbaugh的新的“統(tǒng)一”標(biāo)識符,單一職責(zé)SRP,一個優(yōu)良的系統(tǒng)設(shè)計,強調(diào)模塊間保持低耦合、高內(nèi)聚的關(guān)系,在面向?qū)ο笤O(shè)計中這條規(guī)則同樣適用,所以面向?qū)ο蟮牡谝粋€設(shè)計原則就是:單一職責(zé)原則(SRP,Single Responsibility Principle)。 單一職責(zé),強調(diào)的是職責(zé)的分離,在某種程度上對職責(zé)的理解,構(gòu)成了不同類之間耦合關(guān)系的設(shè)計關(guān)鍵,因此單一職責(zé)原則或多或少成為設(shè)計過程中一個必須考慮的基礎(chǔ)性原則。 1.單一職責(zé)原則(SRP) 一個類,最好只做一件事,只有一個引起它變化的原因。 例如,在一個Game類中,可能會具有兩個不同的職責(zé),一個職責(zé)是維護創(chuàng)建當(dāng)前輪的比賽,另一個職責(zé)是計算總比賽得分。根據(jù)srp原則,著兩個職責(zé)應(yīng)該分離到兩個類中,Game類保持維護創(chuàng)建當(dāng)前輪的比賽,Scorer類負(fù)責(zé)計算比賽的得分。 如何要把這兩個職責(zé)分離到單獨的類中呢? 如果一個類承擔(dān)的職責(zé)過多,等于把這些職責(zé)耦合在了一起。一個職責(zé)的變化可能會削弱或者抑制這個類完成其他職責(zé)的能力。這種耦合會導(dǎo)致脆弱的設(shè)計,當(dāng)變化發(fā)生時,設(shè)計會遭受到意想不到的破壞。 例如,考慮下圖的設(shè)計。Retangle類具有兩方法,如圖。一個方法把矩形繪制在屏幕上,另一個方法計算矩形的面積。,有兩個不同的Application使用Rectangle類,如上圖。一個是計算幾何面積的,Rectangle類會在幾何形狀計算方面給予它幫助。另一個Application實質(zhì)上是繪制一個在舞臺上顯示的矩形。 Rectangle類具有了兩個職責(zé),第一個職責(zé)是提供一個矩形形狀幾何數(shù)據(jù)模型;第二個職責(zé)是把矩形顯示在屏幕上。 對于SRP的違反導(dǎo)致了一些嚴(yán)重的問題。首先,我們必須在計算幾何應(yīng)用程序中包含核心顯示對象的模塊。其次,如果繪制矩形Application發(fā)生改變,也可能導(dǎo)致計算矩形面積Application發(fā)生改變,導(dǎo)致不必要的重新編譯,和不可預(yù)測的失敗。,一個較好的設(shè)計是把這兩個職責(zé)分離到下圖所示的兩個完全不同的類中。這個設(shè)計把Rectangle類中進行計算的部分一道GeometryRectangle類中。 現(xiàn)在矩形繪制方式的改變不會對計算矩形面積的應(yīng)用產(chǎn)生影響了。,1.1 什么是職責(zé),在SRP中,我們把職責(zé)定義為“變化的原因”(a reason for change)。如果你能夠想到多于一個的動機去改變類,那么這個類就具有多于一個的職責(zé)。有時,我們很難注意到這一點。我們習(xí)慣于以組的形式去考慮職責(zé)。 class Modem public : void dial(pno:String): ; void hangup(): ; void send(c:Char): ; void recv(): ; 上述Modem接口,大多數(shù)人會認(rèn)為這個接口看起來非常合理。該接口聲明了4個函數(shù)確實是Modem所具有的功能。然而,該接口卻顯示出了兩個職責(zé),一個是連接管理(dial+hangup),第二個是數(shù)據(jù)通信(send+recv)。 這兩個職責(zé)應(yīng)該被分離開么?這依賴于應(yīng)用程序的變化。 是按照實際情況決定的。如果應(yīng)用程序的變化會影響連接管理,那么設(shè)計就具有僵化的臭味。因為,調(diào)用send和recv的類必須要重新編輯。在這種情況下,這兩個職責(zé)應(yīng)該被分離,這樣做會避免這兩個職責(zé)耦合在一起。 另一方面,如果應(yīng)用程序的變化總是導(dǎo)致這兩方面職責(zé)同時變化,那么就不必分離他們。實際上,分離他們就會具有不必要的復(fù)雜性臭味,1.2 持久化,上圖展示了一種常見的違反SRP的情況,Employee類包含了業(yè)務(wù)邏輯和對于持久層的控制。這兩個職責(zé)在大多數(shù)情況下決不應(yīng)該混合在一起。 業(yè)務(wù)規(guī)則往往會頻繁的變化,而持久化的方式卻不會如此頻繁的變化,并且變化的原因也是完全不同的。把業(yè)務(wù)規(guī)則和持久模塊綁定在一起的做法是不妥的。當(dāng)僵化性和脆弱性的臭味變得強烈,那么就應(yīng)該使用FACADE和PROXY模式對設(shè)計進行重構(gòu),分離這兩個職責(zé)。 小結(jié): SRP是所有原則中最簡單的之一,也是最難正確應(yīng)用的。我們會自然的把職責(zé)結(jié)合在一起。軟件設(shè)計要做的許多內(nèi)容,就是發(fā)現(xiàn)職責(zé)并把那些職責(zé)相互分離。分離的原則也不是教條性的,需要應(yīng)實際需求而定。,2.OCP開閉原則,“Closed for Modification; Open for Extension”是所有面向?qū)ο笤瓌t的核心。軟件設(shè)計本身所追求的目標(biāo)就是封裝變化、降低耦合,而開放封閉原則正是對這一目標(biāo)的最直接體現(xiàn)。其他的設(shè)計原則,很多時候是為實現(xiàn)這一目標(biāo)服務(wù)的,例如以Liskov替換原則實現(xiàn)最佳的、正確的繼承層次,就能保證不會違反開放封閉原則。 OCP的動機很簡單:軟件是變化的。不論是優(yōu)質(zhì)的設(shè)計還是低劣的設(shè)計都無法回避這一問題。OCP說明了軟件設(shè)計應(yīng)該盡可能地使架構(gòu)穩(wěn)定而又容易滿足不同的需求。,為什么要OCP?,通常,對于開發(fā)完的代碼都需要多種測試才能夠投入使用,這包括: 1 設(shè)計人員進行初期的架構(gòu)設(shè)計 2 要經(jīng)過開發(fā)人員的單元測試、集成測試。 3 然后再到測試人員的白盒測試、黑盒測試。 4 最后還要由用戶進行一定的測試。 經(jīng)過漫長的測試,代碼才能夠投入使用。但是軟件產(chǎn)品的維護和升級又是一個永恒的話題,在維護的過程中,你可能要不斷地增加一些小功能;在升級的過程中,你要增加一些較大的功能。 這種功能的擴展,就要求我們改變原有的代碼。但是,對原代碼的修改就會深刻地影響到原來的功能的方方面面: 1 可能對舊代碼引入了新的錯誤,使你不得不對舊代碼進行大規(guī)模的修改。 2 可能引起你不得不重新構(gòu)造系統(tǒng)的架構(gòu)。 3 即使新增的代碼對舊代碼沒有影響,你也不得不對原來的系統(tǒng)做一個全面的測試。 4 經(jīng)過一段時間,也許你認(rèn)為以前代碼更好,更符合用戶需求 所有上述列出來的問題,都是對系統(tǒng)功能進行擴展所不能承受的代價。換句話說,我們設(shè)計出來的系統(tǒng),一定要是擴展性良好的系統(tǒng)。如何才能夠設(shè)計出擴展性良好的系統(tǒng)呢?這就需要在軟件系統(tǒng)設(shè)計時遵守開閉原則,玉帝的智慧,玉帝招安美猴王 的例子 不勞師動眾、不破壞天規(guī)便是“閉”,收仙有道便是“開”。招安之法便是玉帝天庭的“開一閉”原則,通過給美猴王封一個“弼馬溫”的官職,便可使現(xiàn)有系統(tǒng)滿足變化了的需求,而不必更改天庭的既有秩序,如何在OO中引入OCP原則?,把對實體的依賴改為對抽象的依賴就行了。下面的例子說明了這個過程: 05賽季的時候,一輛F1賽車有一臺V10引擎。但是到了06賽季,國際汽聯(lián)修改了規(guī)則,一輛F1賽車只能安裝一臺V8引擎。車隊很快投入了新賽車的研發(fā),不幸的是,從工程師那里得到消息,舊車身的設(shè)計不能夠裝進新研發(fā)的引擎。我們不得不為新的引擎重新打造車身,于是一輛新的賽車誕生了。但是,麻煩的事接踵而來,國際汽聯(lián)頻頻修改規(guī)則,搞得設(shè)計師在“賽車”上改了又改,最終變得不成樣子,只能把它廢棄。,為了能夠重用這輛昂貴的賽車,工程師們提出了解決方案:首先,在車身的設(shè)計上預(yù)留出安裝引擎的位置和管線。然后,根據(jù)這些設(shè)計好的規(guī)范設(shè)計引擎(或是引擎的適配器)。于是,新的賽車設(shè)計方案就這樣誕生了。,做到開閉原則,就注意以下兩點。,1)多使用抽象類 在設(shè)計類時,對于擁有共同功能的相似類進行抽象化處理,將公用的功能部分放到抽象類中,所有的操作都調(diào)用子類。這樣,在需要對系統(tǒng)進行功能擴展時,只需要依據(jù)抽象類實現(xiàn)新的子類即可。如圖10-1所示,在擴展子類時,不僅可以擁有抽象類的共有屬性和共有函數(shù),還可以擁有自定義的屬性和函數(shù)。,2)多使用接口,與抽象類不同,接口只定義子類應(yīng)該實現(xiàn)的接口函數(shù),而不實現(xiàn)公有的功能。在現(xiàn)在大多數(shù)的軟件開發(fā)中,都會為實現(xiàn)類定義接口,這樣在擴展子類時實現(xiàn)該接口。如果要改換原有的實現(xiàn),只需要改換一個實現(xiàn)類即可。 如圖各子類由接口類定義了接口函數(shù),只需要在不同的子類中編寫不同的實現(xiàn)即可,當(dāng)然也可以實現(xiàn)自有的函數(shù)。,Liskov(女程序員)替換原則,在一個軟件系統(tǒng)中,子類應(yīng)該可以替換任何基類能夠出現(xiàn)的地方,并且經(jīng)過替換以后,代碼還能正常工作。 第一個例子:正方形不是長方形 “正方形不是長方形”是一個理解里氏代換原則的最經(jīng)典的例子。在數(shù)學(xué)領(lǐng)域里,正方形毫無疑問是長方形,它是一個長寬相等的長方形。所以,我們開發(fā)的一個與幾何圖形相關(guān)的軟件系統(tǒng)中,讓正方形繼承自長方形是順利成章的事情?,F(xiàn)在,我們截取該系統(tǒng)的一個代碼片段進行分析:,正方形不是長方形,長方形類Rectangle: class Rectangle double length; double width; public : double getLength() return length; void setLength(double height) this.length = length; double getWidth() return width; void setWidth(double width) this.width = width; ,正方形類Square: class Square :public Rectangle public : void setWidth(double width) Rectangle :setLength(width); Rectangle : setWidth(width); void setLength(double length) Rectangle :.setLength(length); Rectangle :.setWidth(length); ,正方形不是長方形,由于正方形的度和寬度必須相等,所以在方法setLength和setWidth中,對長度和寬度賦值相同。類TestRectangle是我們的軟件系統(tǒng)中的一個組件,它有一個resize方法要用到基類Rectangle,resize方法的功能是模擬長方形寬度逐步增長的效果: 測試類TestRectangle: class TestRectangle public: void resize(Rectangle ,正方形不是長方形,我們運行一下這段代碼就會發(fā)現(xiàn),假如我們把一個普通長方形作為參數(shù)傳入resize方法,就會看到長方形寬度逐漸增長的效果,當(dāng)寬度大于長度,代碼就會停止,這種行為的結(jié)果符合我們的預(yù)期;假如我們再把一個正方形作為參數(shù)傳入resize方法后,就會看到正方形的寬度和長度都在不斷增長,代碼會一直運行下去,直至系統(tǒng)產(chǎn)生溢出錯誤。所以,普通的長方形是適合這段代碼的,正方形不適合。 我們得出結(jié)論:在resize方法中,Rectangle類型的參數(shù)是不能被Square類型的參數(shù)所代替,如果進行了替換就得不到預(yù)期結(jié)果。因此,Square類和Rectangle類之間的繼承關(guān)系違反了里氏代換原則,它們之間的繼承關(guān)系不成立,正方形不是長方形。,鴕鳥不是鳥,“鴕鳥非鳥”也是一個理解里氏代換原則的經(jīng)典的例子。“鴕鳥非鳥”的另一個版本是“企鵝非鳥”,這兩種說法本質(zhì)上沒有區(qū)別,前提條件都是這種鳥不會飛。生物學(xué)中對于鳥類的定義:“恒溫動物,卵生,全身披有羽毛,身體呈流線形,有角質(zhì)的喙,眼在頭的兩側(cè)。前肢退化成翼,后肢有鱗狀外皮,有四趾”。所以,從生物學(xué)角度來看,鴕鳥肯定是一種鳥。 我們設(shè)計一個與鳥有關(guān)的系統(tǒng),鴕鳥類順理成章地由鳥類派生,鳥類所有的特性和行為都被鴕鳥類繼承。大多數(shù)的鳥類在人們的印象中都是會飛的,所以,我們給鳥類設(shè)計了一個名字為fly的方法,還給出了與飛行相關(guān)的一些屬性,比如飛行速度(velocity)。 鳥類Bird: class Bird double velocity; public : void fly() /I am flying; ; void setVelocity(double velocity) this.velocity = velocity; ; double getVelocity() return this.velocity; ; 鴕鳥不會飛怎么辦?我們就讓它扇扇翅膀表示一下吧,在fly方法里什么都不做。至于它的飛行速度,不會飛就只能設(shè)定為0了,于是我們就有了鴕鳥類的設(shè)計。 鴕鳥類Ostrich: class Ostrich :public Bird public fly() /I do nothing; ; public setVelocity(double velocity) this.velocity = 0; ; public getVelocity() return 0; ; ,鴕鳥不是鳥,好了,所有的類都設(shè)計完成,我們把類Bird提供給了其它的代碼(消費者)使用?,F(xiàn)在,消費者使用Bird類完成這樣一個需求:計算鳥飛越黃河所需的時間。 對于Bird類的消費者而言,它只看到了Bird類中有fly和getVelocity兩個方法,至于里面的實現(xiàn)細(xì)節(jié),它不關(guān)心,而且也無需關(guān)心,于是給出了實現(xiàn)代碼: 測試類TestBird: class TestBird public: void calcFlyTime(Bird bird) try double riverWidth = 3000; cout.riverWidth / bird.getVelocity()endl; catch() cout“An error occured!“endl ; ; ,鴕鳥不是鳥,如果我們拿一種飛鳥來測試這段代碼,沒有問題,結(jié)果正確,符合我們的預(yù)期,系統(tǒng)輸出了飛鳥飛越黃河的所需要的時間;如果我們再拿鴕鳥來測試這段代碼,結(jié)果代碼發(fā)生了系統(tǒng)除零的異常,明顯不符合我們的預(yù)期。 對于TestBird類而言,它只是Bird類的一個消費者,它在使用Bird類的時候,只需要根據(jù)Bird類提供的方法進行相應(yīng)的使用,根本不會關(guān)心鴕鳥會不會飛這樣的問題,而且也無須知道。它就是要按照“所需時間 = 黃河的寬度 / 鳥的飛行速度”的規(guī)則來計算鳥飛越黃河所需要的時間。 我們得出結(jié)論:在calcFlyTime方法中,Bird類型的參數(shù)是不能被Ostrich類型的參數(shù)所代替,如果進行了替換就得不到預(yù)期結(jié)果。因此,Ostrich類和Bird類之間的繼承關(guān)系違反了里氏代換原則,它們之間的繼承關(guān)系不成立,鴕鳥不是鳥。,4.4 鴕鳥到底是不是鳥?,“鴕鳥到底是不是鳥”,鴕鳥是鳥也不是鳥,這個結(jié)論似乎就是個悖論。產(chǎn)生這種混亂有兩方面的原因: 原因一:對類的繼承關(guān)系的定義沒有搞清楚。 面向?qū)ο蟮脑O(shè)計關(guān)注的是對象的行為,它是使用“行為”來對對象進行分類的,只有行為一致的對象才能抽象出一個類來。 類的繼承關(guān)系就是一種“Is-A”關(guān)系,實際上指的是行為上的“Is-A”關(guān)系,可以把它描述為“Act-As”。 我們再來看“正方形不是長方形”這個例子,正方形在設(shè)置長度和寬度這兩個行為上,與長方形顯然是不同的。長方形的行為:設(shè)置長方形的長度的時候,它的寬度保持不變,設(shè)置寬度的時候,長度保持不變。正方形的行為:設(shè)置正方形的長度的時候,寬度隨之改變;設(shè)置寬度的時候,長度隨之改變。所以,如果我們把這種行為加到基類長方形的時候,就導(dǎo)致了正方形無法繼承這種行為。我們“強行”把正方形從長方形繼承過來,就造成無法達到預(yù)期的結(jié)果。 “鴕鳥非鳥”基本上也是同樣的道理。我們一講到鳥,就認(rèn)為它能飛,有的鳥確實能飛,但不是所有的鳥都能飛。問題就是出在這里。如果以“飛”的行為作為衡量“鳥”的標(biāo)準(zhǔn)的話,鴕鳥顯然不是鳥;如果按照生物學(xué)的劃分標(biāo)準(zhǔn):有翅膀、有羽毛等特性作為衡量“鳥”的標(biāo)準(zhǔn)的話,鴕鳥理所當(dāng)然就是鳥了。鴕鳥沒有“飛”的行為,我們強行給它加上了這個行為,所以在面對“飛越黃河”的需求時,代碼就會出現(xiàn)運行期故障。,鴕鳥到底是不是鳥?,原因二:設(shè)計要依賴于用戶要求和具體環(huán)境。 繼承關(guān)系要求子類要具有基類全部的行為。這里的行為是指落在需求范圍內(nèi)的行為。圖中鳥類具有4個對外的行為,其中2個行為分別落在A和B系統(tǒng)需求中:,系統(tǒng)需求和對象關(guān)系示意圖,A需求期望鳥類提供與飛翔有關(guān)的行為,即使鴕鳥跟普通的鳥在外觀上就是100%的相像,但在A需求范圍內(nèi),鴕鳥在飛翔這一點上跟其它普通的鳥是不一致的,它沒有這個能力,所以,鴕鳥類無法從鳥類派生,鴕鳥不是鳥。 B需求期望鳥類提供與羽毛有關(guān)的行為,那么鴕鳥在這一點上跟其它普通的鳥一致的。雖然它不會飛,但是這一點不在B需求范圍內(nèi),所以,它具備了鳥類全部的行為特征,鴕鳥類就能夠從鳥類派生,鴕鳥就是鳥。 所有派生類的行為功能必須和使用者對其基類的期望保持一致,如果派生類達不到這一點,那么必然違反里氏替換原則。在實際的開發(fā)過程中,不正確的派生關(guān)系是非常有害的。伴隨著軟件開發(fā)規(guī)模的擴大,參與的開發(fā)人員也越來越多,每個人都在使用別人提供的組件,也會為別人提供組件。最終,所有人的開發(fā)的組件經(jīng)過層層包裝和不斷組合,被集成為一個完整的系統(tǒng)。每個開發(fā)人員在使用別人的組件時,只需知道組件的對外裸露的接口,那就是它全部行為的集合,至于內(nèi)部到底是怎么實現(xiàn)的,無法知道,也無須知道。所以,對于使用者而言,它只能通過接口實現(xiàn)自己的預(yù)期,如果組件接口提供的行為與使用者的預(yù)期不符,錯誤便產(chǎn)生了。里氏代換原則就是在設(shè)計時避免出現(xiàn)派生類與基類不一致的行為。,如何正確地運用里氏代換原則,里氏代換原則目的就是要保證繼承關(guān)系的正確性。我們在實際的項目中,是不是對于每一個繼承關(guān)系都得費這么大勁去斟酌?不需要,大多數(shù)情況下按照“Is-A”去設(shè)計繼承關(guān)系是沒有問題的,只有極少的情況下,需要你仔細(xì)處理一下,這類情況對于有點開發(fā)經(jīng)驗的人,一般都會覺察到,是有規(guī)律可循的。最典型的就是使用者的代碼中必須包含依據(jù)子類類型執(zhí)行相應(yīng)的動作的代碼:,動物類Animal: class Animal string name; public: void Animal(String name) = name; void printName() try cout“I am a “ + name + “!“endl; catch() cout“An error occured!“endl; ,貓類Cat: class Cat :public Animal public: Cat(String name) :Animal(name) void Mew() trycout “Mew “endl; catch()cout“An error occured!“endl; ,狗類Dog: public class Dog :public Animal Dog(String name) : Animal (name) void Bark() try cout“Bark “endl; catch() cout“An error occured!“endl; ,測試類:TestAnimal class TestAnimal public: void TestLSP(Animal ,象這種代碼是明顯不符合里氏代換原則的,它給使用者使用造成很大的麻煩, 甚至無法使用,對于以后的維護和擴展帶來巨大的隱患。 實現(xiàn)開閉原則的關(guān)鍵步驟是抽象化,基類與子類之間的繼承關(guān)系就是一種抽象化的體現(xiàn)。因此,里氏代換原則是實現(xiàn)抽象化的一種規(guī)范。 違反里氏代換原則意味著違反了開閉原則,反之未必。里氏代換原則是使代碼符合開閉原則的一個重要保證。,依賴倒置DIP,1、高層模塊不應(yīng)該依賴于低層模塊,二者都應(yīng)該依賴于抽象。 2、抽象不應(yīng)該依賴于細(xì)節(jié),細(xì)節(jié)應(yīng)該依賴于抽象,要針對接口編程,不要針對實現(xiàn)編程。 依賴:在程序設(shè)計中,如果一個模塊a使用/調(diào)用了另一個模塊b,我們稱模塊a依賴模塊b。 高層模塊與低層模塊:往往在一個應(yīng)用程序中,我們有一些低層次的類,這些類實現(xiàn)了一些基本的或初級的操作,我們稱之為低層模塊;另外有一些高層次的類,這些類封裝了某些復(fù)雜的邏輯,并且依賴于低層次的類,這些類我們稱之為高層模塊。 高層模塊包含了一個應(yīng)該程序中的重要的策略選擇和業(yè)務(wù)模型,正是這些高層模塊才使得其所有的應(yīng)用程序區(qū)別于其他,如果高層依賴于低層,那么對低層模塊的改動就會直接影響到高層模塊,從而迫使它們依次做出改動。 具體原則是: 1) 任何變量都不能擁有一個具體類的指針或者引用。 2)任何類都不應(yīng)該從具體類派生。 3)任何方法都不應(yīng)該覆寫基類中已經(jīng)實現(xiàn)的方法。 也就是說應(yīng)當(dāng)使用接口和抽象類進行變量類型聲明、參數(shù)類型聲明、方法返還類型說明,以及數(shù)據(jù)類型的轉(zhuǎn)換等,而不要用具體類進行變量的類型聲明、參數(shù)類型聲明、方法返還類型說明,以及數(shù)據(jù)類型的轉(zhuǎn)換等。要保證做到這一點,一個具體類應(yīng)當(dāng)只實現(xiàn)接口和抽象類中聲明過的方法,而不要給出多余的方法。,依賴倒置DIP,基于這個原則: 設(shè)計類結(jié)構(gòu)的方式應(yīng)該是從上層模塊到底層模塊遵循這樣的結(jié)構(gòu): 上層類-抽象層-底層類。 (High Level Classes(高層模塊) Abstraction Layer(抽象接口層) Low Level Classes(低層模塊)。,缺點:耦合太緊密,Light發(fā)生變化將影響ToggleSwitch。,解決辦法一,將Light作成Abstract,然后具體類繼承自Light。 優(yōu)點:ToggleSwitch依賴于抽象類Light,具有更高的穩(wěn)定性,而BulbLight與TubeLight繼承自Light,可以根據(jù)“開放封閉”原則進行擴展。 只要Light不發(fā)生變化,BulbLight與TubeLight的變化就不會波及ToggleSwitch。 缺點:如果用ToggleSwitch控制一臺電視就很困難了??偛荒茏孴V繼承自Light吧。,解決辦法一,接口隔離ISP,一、ISP簡介(ISP-Interface Segregation Principle): 第一:客戶端不應(yīng)該依賴他不需要的接口也就是對接口的細(xì)化 純潔; 第二:類直接的依賴應(yīng)該建立在最小的接口上面; 第三:建立單一的接口 幾個模塊就要有及格接口 而不是一個龐大的臃腫的接口; 其他:接口是對外的承諾,承諾的越少,月利于開發(fā);但是開發(fā)的過程中也要注意一個度的概念,否則接口太多也不利于維護; 在我們進行設(shè)計的時候,一個重要的工作就是恰當(dāng)?shù)貏澐纸巧徒巧珜?yīng)的接口。因此,這里的接口往往有兩種不同的含義。,二、舉例說明:,1接口對應(yīng)的角色 指一個類型所具有的方法特征的集合,僅僅是一種邏輯上的抽象,接口的劃分就直接帶來類型的劃分。這里,我們可以把接口理解成角色,一個接口只是代表一個角色,每個角色都有它特定的一個接口,這里的這個原則可以叫做角色隔離原則。 例如,我們將電腦的所有功能角色集合為一起,構(gòu)建了一個接口,如圖10-3所示。,此時,我的電腦和你的電腦要實現(xiàn)該接口,就必須實現(xiàn)所有的接口函數(shù),顯然接口混亂,并不能夠滿足實際的需求: 我的電腦可能是用來工作和學(xué)習(xí)的,你的電腦可能是用來看電影、上網(wǎng)和打游戲等娛樂活動的,那我們就可以將電腦的角色劃分為兩類,如圖10-4所示。,2角色對應(yīng)的接口 指某種語言具體的接口定義,有嚴(yán)格的定義和結(jié)構(gòu)。比如Java語言里面的Interface結(jié)構(gòu)。對不同的客戶端,同一個角色提供寬窄不同的接口,也就是定制服務(wù),僅僅提供客戶端需要的行為,客戶端不需要的行為則隱藏起來。 對于圖10-4中的接口定義,如果我的電腦除了工作和學(xué)習(xí)之外,還想上網(wǎng),那就沒辦法了,必須實現(xiàn)娛樂電腦的接口,這樣就必須實現(xiàn)它的所有接口函數(shù)了。此時我們需要將對應(yīng)角色中的接口再進行劃分,如圖10-5所示。,這樣,經(jīng)過以上的劃分,如果我的電腦想增加某一項功能,只需要繼承不同的接口類即可。 由此可見,對接口角色的劃分,是從大的類上進行劃分的;對角色的接口進行的劃分,是對類的接口函數(shù)的劃分。它們兩者由粗到細(xì),實現(xiàn)了接口的完全分離。,迪米特法則(Law of Demeter LoD),又叫做最少知識原則(Least Knowledge Principle,LKP),就是說,一個對象應(yīng)當(dāng)對其他對象有盡可能少的了了解. 迪米特法則最初是用來作為面向?qū)ο蟮南到y(tǒng)設(shè)計風(fēng)格的一種法則,與1987年秋天由Ian Holland在美國東北大學(xué)為一個叫做迪米特(Demeter)的項目設(shè)計提出的,因此叫做迪米特法則LIEB89LIEB86.這條法則實際上是很多著名系統(tǒng),比如火星登陸軟件系統(tǒng),木星的歐羅巴衛(wèi)星軌道飛船的軟件系統(tǒng)的指導(dǎo)設(shè)計原則. 沒有任何一個其他的OO設(shè)計原則象迪米特法則這樣有如此之多的表述方式,如下幾種: (1)只與你直接的朋友們通信(Only talk to your immediate friends) (2)不要跟“陌生人“說話(Dont talk to strangers) (3)每一個軟件單位對其他的單位都只有最少的知識,而且局限于那些本單位密切相關(guān)的軟件單位. 就是說,如果兩個類不必彼此直接通信,那么這兩個類就不應(yīng)當(dāng)發(fā)生直接的相互作用,如果其中的一個類需要調(diào)用另一個類的某一個方法的話,可以通過第三者轉(zhuǎn)發(fā)這個調(diào)用。,合成/聚合復(fù)用原則(Composite/Aggregate Reuse Principle或CARP),定義: 在一個新的對象里面使用一些已有的對象,使之成為新對象的一部分;新的對象通過向這些對象的委派達到復(fù)用這些對象的目的。應(yīng)首先使用合成/聚合,合成/聚合則使系統(tǒng)靈活,其次才考慮繼承,達到復(fù)用的目的。而使用繼承時,要嚴(yán)格遵循里氏代換原則。有效地使用繼承會有助于對問題的理解,降低復(fù)雜度,而濫用繼承會增加系統(tǒng)構(gòu)建、維護時的難度及系統(tǒng)的復(fù)雜度。如果兩個類是“Has-a”關(guān)系應(yīng)使用合成、聚合,如果是“Is-a”關(guān)系可使用繼承?!癐s-A”是嚴(yán)格的分類學(xué)意義上定義,意思是一個類是另一個類的“一種”。而“Has-A”則不同,它表示某一個角色具有某一項

溫馨提示

  • 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)方式做保護處理,對用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對任何下載內(nèi)容負(fù)責(zé)。
  • 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請與我們聯(lián)系,我們立即糾正。
  • 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時也不承擔(dān)用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。

最新文檔

評論

0/150

提交評論