此篇內(nèi)容來自一下兩處
http://blog.joycode.com/microhelper/archive/2004/11/30/40013.aspx
http://www.aygfsteel.com/ghawk/
另:關(guān)于面向?qū)ο笤O(shè)計(jì)的原則比較權(quán)威的是Uncle Bob-- Robert C. Martin的
Principles Of Object Oriented Design
http://c2.com/cgi/wiki?PrinciplesOfObjectOrientedDesign單一職責(zé)原則——SRP
就一個(gè)類而言,應(yīng)該僅有一個(gè)引起它的變化的原因
最簡單,最單純的事情最容易控制,最有效
類的職責(zé)簡單而且集中,避免相同的職責(zé)分散到不同的類之中,避免一個(gè)類承擔(dān)過多的職責(zé)
減少類之間的耦合
當(dāng)需求變化時(shí),只修改一個(gè)地方
組件
每個(gè)組件集中做好一件事情
組件的顆粒度
發(fā)布的成本
可重用的成本
避免寫臃腫的方法
Extract Method
Move Field/Move Class
Extract Method/Extract Class
單一職責(zé)很容易理解,也很容易實(shí)現(xiàn)。所謂單一職責(zé),就是一個(gè)設(shè)計(jì)元素只做一件事。什么是“只做一件事”?簡單說就是少管閑事。現(xiàn)實(shí)中就是如此,如果要你專心做一件事情,任何人都有信心可以做得很出色。但如果,你整天被亂七八糟的事所累,還有心思和精力把每件事都作好么?
????? “單一職責(zé)”就是要在設(shè)計(jì)中為每種職責(zé)設(shè)計(jì)一個(gè)類,彼此保持正交,互不干涉。這個(gè)雕塑(二重奏)就是正交的一個(gè)例子,鋼琴家和小提琴家各自演奏自己的樂 譜,而結(jié)果就是一個(gè)和諧的交響樂。當(dāng)然,真實(shí)世界中,演奏小提琴和彈鋼琴的必須是兩個(gè)人,但是在軟件中,我們往往會把兩者甚至更多攪和到一起,很多時(shí)候只 是為了方便或是最初設(shè)計(jì)的時(shí)候沒有想到。?
??????這樣的例子在設(shè)計(jì)中很常見,書中就給了一個(gè)很好的例子:調(diào)制解調(diào)器。這是一個(gè)調(diào)制 解調(diào)器最基本的功能。但是這個(gè)類事實(shí)上完成了兩個(gè)職責(zé):連接的建立和中斷、數(shù)據(jù)的發(fā)送和接收。顯然,這違反了SRP。這樣做會有潛在的問題:當(dāng)僅需要改變 數(shù)據(jù)連接方式時(shí),必須修改Modem類,而修改Modem類的結(jié)果就是使得任何依賴Modem類的元素都需要重新編譯,不管它是不是用到了數(shù)據(jù)連接功能。 解決的辦法,書中也已經(jīng)給出:重構(gòu)Modem類,從中抽出兩個(gè)接口,一個(gè)專門負(fù)責(zé)連接、另一個(gè)專門負(fù)責(zé)數(shù)據(jù)發(fā)送。依賴Modem類的元素也要做相應(yīng)的細(xì) 化,根據(jù)職責(zé)的不同分別依賴不同的接口。最后由ModemImplementation類實(shí)現(xiàn)這兩個(gè)接口。
開放封閉原則——OCP
軟件實(shí)體(類,模塊,函數(shù))應(yīng)該是可以擴(kuò)展的,但是不可修改的
對更改是封閉的,對模塊擴(kuò)展時(shí),不需要改動原來的代碼
面對抽象而不是面對細(xì)節(jié),抽象比細(xì)節(jié)活的更長
僵化的設(shè)計(jì)——如果程序中一處改動產(chǎn)生連鎖反應(yīng)。
方法
條件case?? if/else 語句
Replace Type Code With Class
Replace Type Code With State/Strategy
Replace Conditional with polymorphism
開閉原則很簡單,一句話:“Closed for Modification; Open for Extension”——“對變更關(guān)閉;對擴(kuò)展開放”。開閉原則其實(shí)沒什么好講的,我將其歸結(jié)為一個(gè)高層次的設(shè)計(jì)總則。就這一點(diǎn)來講,OCP的地位應(yīng)該比SRP優(yōu)先。
OCP的動機(jī)很簡單:軟件是變化的。不論是優(yōu)質(zhì)的設(shè)計(jì)還是低劣的設(shè)計(jì)都無法回避這一問題。OCP說明了軟件設(shè)計(jì)應(yīng)該盡可能地使架構(gòu)穩(wěn)定而又容易滿足不同的需求。
為什么要OCP?答案也很簡單——重用。
“重用”,并不是什么軟件工程的專業(yè)詞匯,它是工程界所共用的詞匯。早在軟件出現(xiàn)前,工程師們就在實(shí)踐“重用”了。比如機(jī)械產(chǎn)品,通過零部 件的組裝得到最終的能夠使用的工具。由于機(jī)械部件的設(shè)計(jì)和制造過程是極其復(fù)雜的,所以互換性是一個(gè)重要的特性。一輛車可以用不同的發(fā)動機(jī)、不同的變速箱、 不同的輪胎……很多東西我們直接買來裝上就可以了。這也是一個(gè)OCP的例子。(可能是由于我是搞機(jī)械出身的吧,所以就舉些機(jī)械方面的例子^_^)。
如何在OO中引入OCP原則?把對實(shí)體的依賴改為對抽象的依賴就行了。下面的例子說明了這個(gè)過程:
05賽季的時(shí)候,一輛F1賽車有一臺V10引擎。但是到了06賽季,國際汽聯(lián)修改了規(guī)則,一輛F1賽車只能安裝一臺V8引擎。車隊(duì)很快投入了新賽車 的研發(fā),不幸的是,從工程師那里得到消息,舊車身的設(shè)計(jì)不能夠裝進(jìn)新研發(fā)的引擎。我們不得不為新的引擎重新打造車身,于是一輛新的賽車誕生了。但是,麻煩 的事接踵而來,國際汽聯(lián)頻頻修改規(guī)則,搞得設(shè)計(jì)師在“賽車”上改了又改,最終變得不成樣子,只能把它廢棄。
為了能夠重用這輛昂貴的賽車,工程師們提出了解決方案:首先,在車身的設(shè)計(jì)上預(yù)留出安裝引擎的位置和管線。然后,根據(jù)這些設(shè)計(jì)好的規(guī)范設(shè)計(jì)引擎(或是引擎的適配器)。于是,新的賽車設(shè)計(jì)方案就這樣誕生了。
?
說到這里,想說一說OO設(shè)計(jì)的一個(gè)誤區(qū)。
學(xué) 習(xí)OO語言的時(shí)候,為了能夠說明“繼承”(或者說“is-a”)這個(gè)概念,教科書上經(jīng)常用實(shí)際生活中的例子來解釋。比如汽車是車,電車是車,F(xiàn)1賽車是汽 車,所以車是汽車、電車、F1賽車的上層抽象。這個(gè)例子并沒有錯(cuò)。問題是,這樣的例子過于“形象”了!如果OO設(shè)計(jì)直接就可以將現(xiàn)實(shí)生活中的概念引用過 來,那也就不需要什么軟件工程師了!OO設(shè)計(jì)的關(guān)鍵概念是抽象。如果沒有抽象,那所有的軟件工程師的努力都是徒勞的。因?yàn)槿绻麤]有抽象,我們只能去構(gòu)造世 界中每一個(gè)對象。上面這個(gè)例子中,我們應(yīng)該看到“引擎”這個(gè)抽象的存在,因?yàn)檐囮?duì)的工程師們?yōu)樗A(yù)留了位置,為它制定了設(shè)計(jì)規(guī)范。
上面這個(gè)設(shè)計(jì)也 實(shí)現(xiàn)了后面要說的DIP(依賴倒置原則)。但是請記住,OCP是OO設(shè)計(jì)原則中高層次的原則,其余的原則對OCP提供了不同程度的支持。為了實(shí)現(xiàn)OCP, 我們會自覺或者不自覺地用到其它原則或是諸如Bridge、Decorator等設(shè)計(jì)模式。然而,對于一個(gè)應(yīng)用系統(tǒng)而言,實(shí)現(xiàn)OCP并不是設(shè)計(jì)目的,我們 所希望的只是一個(gè)穩(wěn)定的架構(gòu)。所以對OCP的追求也應(yīng)該適可而止,不要陷入過渡設(shè)計(jì)。正如Martin本人所說:“No significant program can be 100% closed.”“Closure not complete but strategic”
Liskov替換原則—— LSP?
子類型必須能夠替換它的基類型
主要針對繼承的設(shè)計(jì)原則
所有派生類的行為功能必須和客戶程序?qū)ζ浠愃谕谋3忠恢隆?br />派生類必須滿足基類和客戶程序的約定
IS-A是關(guān)于行為方式的,依賴客戶程序的調(diào)用方式
Extract Supper Class
長方形和正方形
OCP作為OO的高層原則,主張使用“抽象(Abstraction)”和“多態(tài)(Polymorphism)”將設(shè)計(jì)中的靜態(tài)結(jié)構(gòu)改為動態(tài)結(jié)構(gòu),維持設(shè)計(jì)的封閉性。
“抽象”是語言提供的功能。“多態(tài)”由繼承語義實(shí)現(xiàn)。
如此,問題產(chǎn)生了:“我們?nèi)绾稳ザ攘坷^承關(guān)系的質(zhì)量?”
Liskov于1987年提出了一個(gè)關(guān)于繼承的原則“Inheritance should ensure that any property proved about supertype objects also holds for subtype objects.”——“繼承必須確保超類所擁有的性質(zhì)在子類中仍然成立。”也就是說,當(dāng)一個(gè)子類的實(shí)例應(yīng)該能夠替換任何其超類的實(shí)例時(shí),它們之間才具有 is-A關(guān)系。
該原則稱為Liskov Substitution Principle——里氏替換原則。林先生在上課時(shí)風(fēng)趣地稱之為“老鼠的兒子會打洞”。^_^
我們來研究一下LSP的實(shí)質(zhì)。學(xué)習(xí)OO的時(shí)候,我們知道,一個(gè)對象是一組狀態(tài)和一系列行為的組合體。狀態(tài)是對象的內(nèi)在特性,行為是對象的外在特性。LSP所表述的就是在同一個(gè)繼承體系中的對象應(yīng)該有共同的行為特征。
這一點(diǎn)上,表明了OO的繼承與日常生活中的繼承的本質(zhì)區(qū)別。舉一個(gè)例子:生物學(xué)的分類體系中把企鵝歸屬為鳥類。我們模仿這個(gè)體系,設(shè)計(jì)出這樣的類和關(guān)系。
?
類“鳥”中有個(gè)方法fly,企鵝自然也繼承了這個(gè)方法,可是企鵝不能飛阿,于是,我們在企鵝的類中覆蓋了fly方法,告訴方法的調(diào)用者:企 鵝是不會飛的。這完全符合常理。但是,這違反了LSP,企鵝是鳥的子類,可是企鵝卻不能飛!需要注意的是,此處的“鳥”已經(jīng)不再是生物學(xué)中的鳥了,它是軟 件中的一個(gè)類、一個(gè)抽象。
有人會說,企鵝不能飛很正常啊,而且這樣編寫代碼也能正常編譯,只要在使用這個(gè)類的客戶代碼中加一句判斷就行了。但是,這就是問題所 在!首先,客戶代碼和“企鵝”的代碼很有可能不是同時(shí)設(shè)計(jì)的,在當(dāng)今軟件外包一層又一層的開發(fā)模式下,你甚至根本不知道兩個(gè)模塊的原產(chǎn)地是哪里,也就談不 上去修改客戶代碼了。客戶程序很可能是遺留系統(tǒng)的一部分,很可能已經(jīng)不再維護(hù),如果因?yàn)樵O(shè)計(jì)出這么一個(gè)“企鵝”而導(dǎo)致必須修改客戶代碼,誰應(yīng)該承擔(dān)這部分 責(zé)任呢?(大概是上帝吧,誰叫他讓“企鵝”不能飛的。^_^)“修改客戶代碼”直接違反了OCP,這就是OCP的重要性。違反LSP將使既有的設(shè)計(jì)不能封 閉!
修正后的設(shè)計(jì)如下:
?
但是,這就是LSP的全部了么?書中給了一個(gè)經(jīng)典的例子,這又是一個(gè)不符合常理的例子:正方形不是一個(gè)長方形。這個(gè)悖論的詳細(xì)內(nèi)容能在網(wǎng)上找到,我就不多廢話了。
LSP并沒有提供解決這個(gè)問題的方案,而只是提出了這么一個(gè)問題。
于是,工程師們開始關(guān)注如何確保對象的行為。1988年,B. Meyer提出了Design by Contract(契約式設(shè)計(jì))理論。DbC從形式化方法中借鑒了一套確保對象行為和自身狀態(tài)的方法,其基本概念很簡單:
- 每個(gè)方法調(diào)用之前,該方法應(yīng)該校驗(yàn)傳入?yún)?shù)的正確性,只有正確才能執(zhí)行該方法,否則認(rèn)為調(diào)用方違反契約,不予執(zhí)行。這稱為前置條件(Pre-condition)。
- 一旦通過前置條件的校驗(yàn),方法必須執(zhí)行,并且必須確保執(zhí)行結(jié)果符合契約,這稱之為后置條件(Post-condition)。
- 對象本身有一套對自身狀態(tài)進(jìn)行校驗(yàn)的檢查條件,以確保該對象的本質(zhì)不發(fā)生改變,這稱之為不變式(Invariant)。
以上是單個(gè)對象的約束條件。為了滿足LSP,當(dāng)存在繼承關(guān)系時(shí),子類中方法的前置條件必須與超類中被覆蓋的方法的前置條件相同或者更寬松;而子類中方法的后置條件必須與超類中被覆蓋的方法的后置條件相同或者更為嚴(yán)格。
一些OO語言中的特性能夠說明這一問題:
- 繼承并且覆蓋超類方法的時(shí)候,子類中的方法的可見性必須等于或者大于超類中的方法的可見性,子類中的方法所拋出的受檢異常只能是超類中對應(yīng)方法所拋出的受檢異常的子類。
public?class?SuperClass{
????public?void?methodA()?throws?IOException{}
}
public?class?SubClassA?extends?SuperClass{
????//this?overriding?is?illegal.
????private?void?methodA()?throws?Exception{}
}
public?class?SubClassB?extends?SuperClass{
????//this?overriding?is?OK.
????public?void?methodA()?throws?FileNotFoundException{}
}
- 從Java5開始,子類中的方法的返回值也可以是對應(yīng)的超類方法的返回值的子類。這叫做“協(xié)變”(Covariant)
public?class?SuperClass?{
????public?Number?caculate(){
????????return?null;
????}
}
public?class?SubClass?extends?SuperClass{
????//only?compiles?in?Java?5?or?later.
????public?Integer?caculate(){
????????return?null;
????}
}
可以看出,以上這些特性都非常好地遵循了LSP。但是DbC呢?很遺憾,主流的面向?qū)ο笳Z言(不論是動態(tài)語言還是靜態(tài)語言)還沒有加入對DbC的支持。但是隨著AOP概念的產(chǎn)生,相信不久DbC也將成為OO語言的一個(gè)重要特性之一。
依賴倒置原則—— DIP?
a:高層模塊不應(yīng)依賴于底層模塊,兩者都應(yīng)該依賴于抽象
b:抽象不應(yīng)該依賴于細(xì)節(jié),細(xì)節(jié)應(yīng)該依賴于抽象
如何解釋倒置
高層依賴底層,重用變得困難,而最經(jīng)常重用的就是framework和各個(gè)獨(dú)立的功能組件
高層依賴底層,底層的改動直接反饋到高層,形成依賴的傳遞
實(shí)例
Ioc模式
DomainObject / DomianObjectDataService
接口隔離原則—— ISP?
使用多個(gè)專門的接口比使用單一的總接口總要好。換而言之,從一個(gè)客戶類的角度來講:一個(gè)類對另外一個(gè)類的依賴性應(yīng)當(dāng)是建立在最小接口上的。
原則過于臃腫的接口是對接口的污染。不應(yīng)該強(qiáng)迫客戶依賴于它們不用的方法。
My object-oriented umbrella(摘自Design Patterns Explained)
Let me tell you about my great umbrella. It is large enough to get into! In fact, three or four other people can get in it with me. While we are in it, staying out of the rain, I can move it from one place to another. It has a stereo system to keep me entertained while I stay dry. Amazingly enough, it can also condition the air to make it warmer or colder. It is one cool umbrella.
My umbrella is convenient. It sits there waiting for me. It has wheels on it so that I do not have to carry it around. I don't even have to push it because it can propel itself. Sometimes, I will open the top of my umbrella to let in the sun. (Why I am using my umbrella when it is sunny outside is beyond me!)
In Seattle, there are hundreds of thousands of these umbrellas in all kinds of colors. Most people call them cars.
實(shí)現(xiàn)方法:
1、?使用委托分離接口
2、?使用多重繼承分離接口
想到一個(gè)朋友說的話:所有的接口都只有一個(gè)方法,具體的類根據(jù)自己需要什么方法去實(shí)現(xiàn)接口