1. 程式人生 > >面向物件設計原則、模式開篇

面向物件設計原則、模式開篇

記得畢業後剛上班不久,一個同學打電話給我求救,說他正在做筆試題,要寫幾個常見的Design Pattern,然後問我什麼是Design Pattern,叫我趕緊告訴他幾個。身為菜鳥的我,要能回答如此問題還能叫菜鳥?因此要他自己搞定,結果可想而知,他面試杯具了。我也慢慢忘了這檔事。然而不久後的一天,我面試時也被問到了這個Design Pattern,考官要我說下對於Design Pattern的理解,然後講幾個常見的Pattern,我除了明白這兩個單詞翻譯出來叫“設計模式”之外,對它一無所知,面試當然同樣杯具了。在一個坑裡看別人摔了一次,自己也摔了一次,我才關注這個Design Pattern

是神馬呢?於是百度了一下,瞭解了這個原來是面向物件中的一門大學問。於是馬上從噹噹上買了號稱聖經的GOF的《設計模式:可複用面向物件軟體的基礎》,開始啃起來。當時的水平是C++的語法還得藉助教科書才說得清楚,寫程式碼基本上是師傅手把手的教才能開筆(開鍵盤?),可想而知,那對我來說就是天書。於是,繼續求助於網路,下了諸如《設計模式精解-GoF 23種設計模式解析附C++實現原始碼》,《常見設計模式的解析和實現》,《Head First設計模式》,《大話設計模式》慢慢研究起來(CSDN真是好地方,大部分計算機的電子檔都可以在上面找到)。網上對於《Head First設計模式》的評價非常高,可篇幅實在太長了,我也不習慣那圖文並茂的方式,可能是對老外思維模式不習慣吧,所以這本書沒讀幾頁。但對《大話設計模式》還是仔細拜讀了的,感覺作者把抽象的理論用輕鬆詼諧的言語表達出來了,出於對作者的敬意,我還特意買了本紙版的。

隨著工齡的增長,對設計模式也慢慢有了點了解。先借用《設計模式精解-GoF 23種設計模式解析》的原文來表述一下:面向物件系統的分析和設計實際上追求的就是兩點,一是高內聚(Cohesion),而是低耦合(Coupling)。這也是我們軟體設計所準求的,因此無論是OO中的封裝、繼承、多型,還是我們的設計模式的原則和例項都是在為了這兩個目標努力著、貢獻著。設計模式體現的是一種思想,而思想則是指導行為的一切,理解和掌握了設計模式,並不是說記住了23種(或更多)設計場景和解決策略(實際上這也是很重要的一筆財富),實際接受的是一種思想的薰陶和洗禮,等這種思想融入到了你的思想中後,你就會不自覺地使用這種思想去進行你的設計和開發,這一切才是最重要的。

俗話說,好記心不如爛筆頭,曾經對設計模式的一些學習筆記和心得也做過文字記錄,在以前的公司電腦硬碟中,由於資訊保安,沒法帶出來,離職後數以萬字的一些記錄都隨著離職而丟失了,我覺得隨著丟失的不僅僅是記錄本身,還有個人的精神財富(這是真心話,感覺四五年時間就積累了一點筆記)。因為零零碎碎的記錄了好多東西,包括好幾年的學習筆記,遇到的一些問題的解決方法,一些較常用的有用的原始碼。好在現在可以上網了,對於以前學過的很多東西都快忘得差不多了,於是再重溫一下,並記錄下來。

Rorbet C. Martin在《敏捷軟體開發:原則、模式與實踐》一書中提到:

個體和互動勝過過程和互動

可以工作的軟體勝過面面俱到的文件

客戶合作勝過合同談判

響應變化勝過遵循計劃

雖然右項也具有價值

但我們認為左項具有更大的價值

設計模式是面向物件設計在實踐中積累出來的,其追求的高內聚低耦合也必然需要遵循一些基本準則,所以總結一下ASD中提到的面向物件設計的原則:

1SRPSingle Responsibility Principle):單一職責原則

對於一個類而言,應該僅有一個引起它變化的原因。

因為如果一個類承擔了多於一個的職責,那麼引起它變化的原因就會有多個。

如果一個類承擔的職責過多,就等於把這些職責耦合在了一起。一個職責的變化可能會消弱或者會抑制這個類完成其他職責的能力。這種耦合會導致脆弱的設計,當變化發生時,設計會遭到意想不到的破壞。

2OCPOpen-Closed Principle):開放封閉原則

軟體實體(類、模組、函式等)應該是可以擴充套件的,但是不可以修改。

如果程式中的一處改動就會產生連鎖反應,導致一系列相關模組的改動,那麼設計就具有僵化性的臭味。如果正確的應用OCP,那麼以後再進行同樣的改動時,就只需要新增新的程式碼,而不比改動已經正常執行的程式碼。

遵循開放封閉原則設計出的模組具有兩個主要特徵。他們是:

1.       對於擴充套件是開放的。

2.       對於更改是封閉的。

3LSPLiskov Substitution Principle):Liskov替換原則

子型別必須能夠替換他們的基類。

LSPOCP的基本保證。是在構建類的繼承結構的過程中需要遵循的基本原則,什麼時候該用,什麼時候不能用,避免繼承的濫用。

經典案例就是ASD中關於矩形和正方形的案例,反映了現實世界中概念和OO概念的區別,很多情況都需要在做OOD的時候仔細考慮,只有符合了LSP才可能實現OCP。因為OOD中的IS-A關係式就行為方式而言,行為方式是可以進行合理假設的,是客戶程式所依賴的。

4DIPDependency Inversion Principle):依賴倒置原則

抽象不應該依賴於細節,細節應該依賴於抽象。

高層模組不應該依賴於低層模組。二者都應該依賴於抽象。

如果高層模組獨立於低層模組,那麼高層模組就也可以非常容易地被重用。該原則是框架(framework)設計的核心原則。

根據這個啟發式規則,可知:

1.       任何變數都不應該持有一個只想具體類的指標或者引用。

2.       任何類都不應該從具體類派生。

3.       任何方法都不應該覆寫他的任何基類中的已經實現了的方法。

5ISPInterface Segregation Principle):介面隔離原則

不應該強迫客戶依賴於他不是用的方法。介面屬於客戶,不屬於他所在的類層次結構。

如果強迫客戶程式依賴於那些他們不是用的方法,那麼這些客戶程式就面臨著由於這些未是用的方法的改變所帶來的變更。這無意中導致了所有客戶程式之間的耦合。換種說法,如果一個客戶程式依賴於一個含有他不是用的方法的類,但是其他客戶程式卻要使用該方法,那麼當其他客戶要求這個類改變時,就會影響到這個客戶程式。我們希望儘可能地避免這種耦合,因此我們希望分離介面,其實就是說盡量讓介面小,儘量讓客戶只接觸到他需要的介面。

6REPRelease Reuse Equivalency Principle):重用釋出等價原則

重用的粒度就是釋出的粒度。

重用的定義:可以重用的程式碼是指bug的改修和功能增加的改修的原因,程式碼版本要升級的場合,利用這些程式碼的系統不需要看具體的程式碼,只要適當的時機替換掉靜態的庫就能夠正常工作。

包:是相關的類的集合,換言之一個類基本上都和其他的一些有依賴關係。因此、釋出的最小單位一般認為是一個包。

REP重用釋出等價原則是針對包的設計來說的。

重用的單位和釋出的單位等價

包裡面包含的所有類都是可以重用的。可以重用的包中不能包含不可重用的類。因為不可重用的類參照了其他元件,包含這個類的這個包就變成不能重用了。

7CCPThe Common Closure Principle):共同封閉原則

包中的所有類對於同一類性質的變化應該是共同封閉的。一個變化若對一個包產生影響,則將對該包中的所有類產生影響,而對於其他的包不造成任何影響。

即因為某個同樣的原因而需要修改的所有類組合進一個包裡。如果2個類從物理上或者從概念上聯絡得非常緊密,它們通常一起發生改變,那麼它們應該屬於同一個包。

CCP從軟體功能的角度上為我們規範了包設計的一個原則:在設計包時,相互之間緊密關聯的類應該放在同一包裡。

8CRPCommon Reuse Principle):共同重用原則

一個包中的所有類應該是共同重用的。如果重用了包中的一個類,那麼就要重用包中的所有類。

CRP從使用者觀點的角度上為我們規範了包設計的原則:在設計包時,包中應該包含的元素要麼都可以重用,要麼都不可以重用。

9ADPAcyclic Dependencies Principle):無環依賴原則

在包的依賴關係圖中不允許存在環。

包之間的依賴結構必須是一個直接的無環圖形(DAG)。也就是說,在依賴結構中不允許出現環(迴圈依賴)。在C++中體現為標頭檔案的迴圈包含。

比如A依賴BB依賴CC依賴A,我們修改了B並需要釋出B的一個新的版本,因為B依賴C,所以釋出時應該包含C,但C同時又依賴A,所以又應該把A也包含進發布版本里。也就是說,依賴結構中,出現在環內的所有包都不得不一起釋出。它們形成了一個高耦合體,當專案的規模大到一定程度,包的數目變多時,包與包之間的關係便變得錯綜複雜,各種測試也將變得非常困難,常常會因為某個不相關的包中的錯誤而使得測試無法繼續。而釋出也變得複雜,需要把所有的包一起釋出,無疑增加了釋出後的驗證難度。所以要避免出現環依賴。

2種方法可以打破這種迴圈依賴關係:第一種方法是建立新的包,第二種方法是使用DIP(依賴倒置原則)和ISP(介面分隔原則)設計原則。

10SDPStable Dependencies Principle):穩定依賴原則

朝著穩定的方向進行依賴。

一個包的抽象程度越高,它的穩定性就越高。反之,它的穩定性就越低。一個穩定的包必須是抽象的,反之,不穩定的包必須是具體的。

不穩定的(容易改變的)包處於上層,它們是具體的包實現

穩定的(不容易改變的)包處於下層,不容易改變,但容易擴充套件。介面比實現(具體的執行程式碼)在內在特性上更具有穩定性

11SAPStable Abstractions Principle):穩定抽象原則

包的抽象程度應該和其穩定度一致。

穩定的包應該是抽象的(由抽象類或介面構成),不穩定的包應該是具體的(由具體的實現類構成)。

12CARPComposite/Aggregate Reuse Principle):儘量使用組合/聚合,儘量不要使用類繼承。

原因:物件的繼承關係是在編譯時就定義好了,所以無法再執行時改變從父類繼承的實現。子類的實現與它的父類有非常緊密的依賴關係,以至於父類實現中的任何變化必然會導致子類發生變化。當你需要複用子類時,如果繼承襲來的實現不適合解決新的問題,則父類必須重寫或被其他更適合的類替換。這種依賴關係限制了靈活性並最終限制了複用性。

有限使用物件的組合/聚合將有助於你保持每個類被封裝,並被集中在單個任務上。這樣類和類繼承層次會保持較小規模,並且不太可能增長為不可控制的龐然大物。

13Lod:迪米特法則,也叫最少知識原則

如果兩個類不必彼此直接通訊,那麼這兩個類就不應當發生直接的相互作用。如果其中一個類需要呼叫另一個類的某一個方法的話,可以通過第三者轉發這個呼叫。

迪米特法則首先強調的前提是在類的結構設計上,每一個類都應當儘量降低成員的訪問許可權。

通常將設計模式做如下分類:

1建立型模式:

建立型模式抽象了例項化過程。它們幫助一個系統獨立於如何建立、組合和表示它的那些物件。一個類建立型模式使用繼承改變被例項化的類,而一個物件建立型模式將例項化委託給另一個物件。

1.1 Factory模式

1.2 AbstactFactory模式

1.3 Singleton模式

1.4 Builder模式

1.5 Prototype模式

2結構型模式:

結構型模式涉及到如何組合類和物件以獲得更大的結構。結構型類模式採用繼承機制來組合介面或實現。結構型物件模式不是對介面和實現進行組合,而是描述瞭如何對一些物件進行組合,從而實現新功能的一些方法。因為可以在執行時刻改變物件組合關係,所以物件組合方式具有更大的靈活性,而這種機制用靜態類組合是不可能實現的。

2.1 Bridge模式

2.2 Adapter模式

2.3 Decorator模式

2.4 Composite模式

2.5 Flyweight模式

2.6 Facade模式

2.7 Proxy模式

3行為模式:

行為模式涉及到演算法和物件間職責的分配。行為模式不僅描述物件或類的模式,還描述它們之間的通訊模式。這些模式刻劃了在執行時難以跟蹤的複雜的控制流。它們將你的注意力從控制流轉移到物件間的聯絡方式上來。行為類模式使用繼承機制在類間分派行為。行為物件模式使用物件複合而不是繼承。一些行為物件模式描述了一組對等的物件怎樣相互協作以完成其中任一個物件都無法單獨完成的任務。

3.1 Template模式

3.2 Strategy模式

3.3 State模式

3.4 Observer模式

3.5 Memento模式

3.6 Mediator模式

3.7 Command模式

3.8 Visitor模式

3.9 Chain of Responsibility模式

3.10 Iterator模式

3.11 Interpreter模式