天天看點

面向對象設計原則、模式開篇

記得畢業後剛上班不久,一個同學打電話給我求救,說他正在做筆試題,要寫幾個常見的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中提到的面向對象設計的原則:

1、SRP(Single Responsibility Principle):單一職責原則

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

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

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

2、OCP(Open-Closed Principle):開放封閉原則

軟體實體(類、子產品、函數等)應該是可以擴充的,但是不可以修改。

如果程式中的一處改動就會産生連鎖反應,導緻一系列相關子產品的改動,那麼設計就具有僵化性的臭味。如果正确的應用OCP,那麼以後再進行同樣的改動時,就隻需要添加新的代碼,而不比改動已經正常運作的代碼。

遵循開放—封閉原則設計出的子產品具有兩個主要特征。他們是:

1.       對于擴充是開放的。

2.       對于更改是封閉的。

3、LSP(Liskov Substitution Principle):Liskov替換原則

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

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

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

4、DIP(Dependency Inversion Principle):依賴倒置原則

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

高層子產品不應該依賴于低層子產品。二者都應該依賴于抽象。

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

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

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

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

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

5、ISP(Interface Segregation Principle):接口隔離原則

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

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

6、REP(Release Reuse Equivalency Principle):重用釋出等價原則

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

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

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

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

重用的機關和釋出的機關等價

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

7、CCP(The Common Closure Principle):共同封閉原則

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

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

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

8、CRP(Common Reuse Principle):共同重用原則

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

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

9、ADP(Acyclic Dependencies Principle):無環依賴原則

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

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

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

有2種方法可以打破這種循環依賴關系:第一種方法是建立新的包,第二種方法是使用DIP(依賴倒置原則)和ISP(接口分隔原則)設計原則。

10、SDP(Stable Dependencies Principle):穩定依賴原則

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

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

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

穩定的(不容易改變的)包處于下層,不容易改變,但容易擴充。接口比實作(具體的運作代碼)在内在特性上更具有穩定性

11、SAP(Stable Abstractions Principle):穩定抽象原則

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

穩定的包應該是抽象的(由抽象類或接口構成),不穩定的包應該是具體的(由具體的實作類構成)。

12、CARP(Composite/Aggregate Reuse Principle):盡量使用組合/聚合,盡量不要使用類繼承。

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

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

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

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

迪米特法則首先強調的前提是在類的結構設計上,每一個類都應當盡量降低成員的通路權限。

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

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模式

繼續閱讀