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