一個(gè)良好的面向?qū)ο笤O(shè)計(jì)需要遵循一些基本原則,如單一職責(zé)原則(SRP)、開放-封閉原則(OCP)、Liskov替換原則(LSP)、依賴倒置原則(DIP)、接口分離原則(ISP)等。
1、 單一職責(zé)原則(SRP)
描述:就一個(gè)類而言,應(yīng)該僅有一個(gè)引起它變化的原因。
應(yīng)用:在構(gòu)造對(duì)象時(shí),將對(duì)象的不同職責(zé)分離至兩個(gè)或多個(gè)類中,確保引起該類變化的原因只有一個(gè)。
帶來(lái)的好處:提高內(nèi)聚、降低耦合。
個(gè)人觀點(diǎn):該原則可以有效降低耦合,減少對(duì)不必要資源的引用。但后果是造成源文件增多,給管理帶來(lái)不便,所以在實(shí)際應(yīng)用中,可以對(duì)經(jīng)常使用或經(jīng)常需要改動(dòng)的模塊應(yīng)用該原則。
2、 開放-封閉原則(OCP)
描述:"對(duì)于擴(kuò)展是開放的"(Open for extension)。這意味著模塊的行為是可以擴(kuò)展的。當(dāng)應(yīng)用的需求改變時(shí),可以對(duì)模塊進(jìn)行擴(kuò)展,使其具有滿足改變的新行為。也就是說(shuō),我們可以改變模塊的功能。"對(duì)于更改是封閉的"(Close for modification)。對(duì)模塊行為進(jìn)行擴(kuò)展時(shí),不必改動(dòng)模塊的源代碼或者二進(jìn)制代碼。
應(yīng)用:高級(jí)語(yǔ)言中的接口與虛擬類。
帶來(lái)的好處:提高靈活性、可重用性、可維護(hù)性。
個(gè)人觀點(diǎn):OCP的關(guān)鍵是抽象,抽象的目的是創(chuàng)建一個(gè)固定卻能夠描述一組任意個(gè)可能行為的基類。而這一組可能的行為則表現(xiàn)為派生類。對(duì)于基類的更改是封閉的,所以它里邊的方法一旦確定就不能更改(對(duì)接口里的方法進(jìn)行更改將帶來(lái)災(zāi)難性的后果)。模塊通過(guò)抽象基類進(jìn)行引用,對(duì)派生類的擴(kuò)展并不影響整個(gè)模塊,所以它是開放的。遵循OCP的代價(jià)也是昂貴的,創(chuàng)建正確的抽象是要花費(fèi)開發(fā)時(shí)間和精力的,同時(shí)抽象也增加了軟件設(shè)計(jì)的復(fù)雜性。因此有效的預(yù)知變化是OCP設(shè)計(jì)的要點(diǎn),這需要我們進(jìn)行適當(dāng)?shù)恼{(diào)查,提出正確的問題,并利用我們的經(jīng)驗(yàn)和一般常識(shí)來(lái)做出判斷。正確的做法是,只對(duì)程序中頻繁變化的部分做出抽象,拒絕不成熟的抽象和抽象本身一樣重要。
3、 Liskov替換原則(LSP)
描述:若對(duì)每個(gè)類型S的對(duì)象O1,都存在一個(gè)類型T的對(duì)象O2,使得在所有針對(duì)T編寫的程序P中,用O1替換O2后,程序P行為功能不變,則S是T的子類型。
應(yīng)用:在實(shí)現(xiàn)繼承時(shí),子類型(subtype)必須能替換掉它們的基類型(base type)。如果一個(gè)軟件實(shí)體使用的是基類的話那么也一定適用于子類。但反過(guò)來(lái)的代換不成立。
個(gè)人觀點(diǎn): LSP是使OCP成為可能的主要原則之一,對(duì)LSP的違反將導(dǎo)致對(duì)OCP的違反,同時(shí)二者是OOD中抽象和多態(tài)的理論基礎(chǔ),在OOPL中表現(xiàn)為繼承。在高級(jí)語(yǔ)言(JAVA、C#)中,只要我們嚴(yán)格按照接口和虛擬類的語(yǔ)法規(guī)范來(lái)做就能很好遵循此原則,另外我們還應(yīng)該避免一些更微妙的違規(guī)情況。舉個(gè)例子,正方形和矩形,矩形可以做為正方形的基類,因?yàn)檎叫我彩且环N矩形,但對(duì)于正方形來(lái)說(shuō),setWidth()和setHeight()是冗余的,且容易引起錯(cuò)誤,這樣的設(shè)計(jì)就違反了LSP原則。如果有兩個(gè)具體類A和B之間的關(guān)系違反了LSP,可以在以下兩種重構(gòu)方案中選擇一種:1 .創(chuàng)建一個(gè)新的抽象類C,作為兩個(gè)具體類的超類,將A和B共同的行為移動(dòng)到C中,從而解決A和B行為不完全一致的問題。 2 .從B到A的繼承關(guān)系改寫為委派關(guān)系。
4、 依賴倒置原則(DIP)
描述:A .高層模塊不應(yīng)該依賴于低層模塊。二者都應(yīng)該依賴于抽象。B .抽象不應(yīng)該依賴于細(xì)節(jié)。細(xì)節(jié)應(yīng)該依賴于抽象。
應(yīng)用:要依賴抽象,不要依賴于具體。即針對(duì)接口編程,不要針對(duì)實(shí)現(xiàn)編程。針對(duì)接口編程的意思是,應(yīng)當(dāng)使用接口和抽象類進(jìn)行變量的類型聲明、參量的類型聲明,方法的返還類型聲明,以及數(shù)據(jù)類型的轉(zhuǎn)換等。不要針對(duì)實(shí)現(xiàn)編程的意思就是說(shuō),不應(yīng)當(dāng)使用具體類進(jìn)行變量的類型聲明、參量的類型聲明,方法的返還類型聲明,以及數(shù)據(jù)類型的轉(zhuǎn)換等。
結(jié)論:DIP雖然強(qiáng)大,但卻不易實(shí)現(xiàn),因?yàn)橐蕾嚨罐D(zhuǎn)的緣故,對(duì)象的創(chuàng)建很可能要使用對(duì)象工廠,以避免對(duì)具體類的直接引用,此原則的使用將導(dǎo)致大量的類文件。給維護(hù)帶來(lái)不必要的麻煩。所以,正確的做法是只對(duì)程序中頻繁變化的部分進(jìn)行依賴倒置。
5、 接口隔離原則(ISP)
描述:不要強(qiáng)迫客戶依賴于它們不用的方法。
應(yīng)用:一個(gè)類對(duì)另外一個(gè)類的依賴性應(yīng)當(dāng)是建立在最小的接口上的。如果客戶端只需要某一些方法的話,那么就應(yīng)當(dāng)向客戶端提供這些需要的方法,而不要提供不需要的方法。提供接口意味著向客戶端作出承諾,過(guò)多的承諾會(huì)給系統(tǒng)的維護(hù)造成不必要的負(fù)擔(dān)。
結(jié)論:使用多個(gè)專門的接口比使用單一的接口要好。
遵循以上原則,可以使我們的軟件更具靈活性,強(qiáng)壯性。但靈活是需要付出代價(jià)的,由多態(tài)帶來(lái)的性能損失就是最明顯的一個(gè)問題。所以我們需要權(quán)衡,需要做出選擇,在靈活與性能之間做出選擇。
追本溯源,促使我們使用這些原則的原因是為了滿足需求的變更,于是需求分析就顯得格外重要。然而不管怎么充分的需求分析都可能遭遇需求變更,于是預(yù)測(cè)變化就成了一個(gè)讓人頭痛的事。還是讓我們來(lái)看看敏捷設(shè)計(jì)(XP)是怎么解決這些問題的:"敏捷開發(fā)人員不會(huì)對(duì)一個(gè)龐大的預(yù)先設(shè)計(jì)應(yīng)用那些原則和模式,相反,這些原則和模式被應(yīng)用在一次次的迭代中,力圖使代碼以及代碼所表達(dá)的設(shè)計(jì)保持干凈。"也就是說(shuō)敏捷設(shè)計(jì)通過(guò)快速的迭代來(lái)刺激變化,讓這些變化及早暴露,再根據(jù)變化進(jìn)行相應(yīng)改動(dòng)。很明顯這要比一次性完整設(shè)計(jì)輕松容易的多。
最后引用透明在書評(píng)中的一句話來(lái)結(jié)束這篇blog。"軟件開發(fā)的全部藝術(shù)就是權(quán)衡:在簡(jiǎn)單與復(fù)雜之間權(quán)衡,在一種方案與另一種方案之間權(quán)衡。如果把每個(gè)問題、每個(gè)權(quán)衡的利弊都考慮得清清楚楚,恐怕開發(fā)一個(gè)應(yīng)用程序的成本會(huì)高得驚人。所以,很多時(shí)候我們更依賴自己的審美眼光,用平靜的心去設(shè)計(jì)一個(gè)賞心悅目的系統(tǒng)。"
- 作者: for_job 2004年08月27日, 星期五 16:19