




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