設(shè)計模式簡介:
什么是設(shè)計模式?每一個模式描述了一個在我們周圍不斷重復(fù)發(fā)生的問題,以及該問題的解決方案的核心。這樣,你就能一次又一次地使用該方案而不必做重復(fù)勞動。
歷史性著作《設(shè)計模式:可復(fù)用面向?qū)ο筌浖幕A(chǔ)》一書中描述了種經(jīng)典面向?qū)ο笤O(shè)計模式,創(chuàng)立了模式在軟件設(shè)計中的地位。由于《設(shè)計模式》一書確定了設(shè)計模式的地位,通常所說的設(shè)計模式隱含地表示“面向?qū)ο笤O(shè)計模式”。但這并不意味“設(shè)計模式”就等于“面向?qū)ο笤O(shè)計模式”。GOF(group of four)
底層思維:向下,如何把握機(jī)器底層從微觀理解對象構(gòu)造
語言構(gòu)造 編譯轉(zhuǎn)換 內(nèi)存模型 運(yùn)行時機(jī)制
抽象思維:向上,如何將我們的周圍世界抽象為程序代碼
面向?qū)ο?組件封裝 設(shè)計模式 架構(gòu)模式
向下:深入理解三大面向?qū)ο髾C(jī)制
封裝,隱藏內(nèi)部實(shí)現(xiàn)? ? 繼承,復(fù)用現(xiàn)有代碼? ? 多態(tài),改寫對象行為
向上:深刻把握面向?qū)ο髾C(jī)制所帶來的抽象意義,理解如何使用這些機(jī)制來表達(dá)現(xiàn)實(shí)世界,掌握什么是“好的面向?qū)ο笤O(shè)計”。
軟件設(shè)計復(fù)雜的根本原因:1.客戶需求的變化;2.技術(shù)平臺的變化;3.開發(fā)團(tuán)隊(duì)的變化;4.市場環(huán)境的變化。所以代碼的復(fù)用性對于效率的提升是非常行之有效的。(復(fù)用指編譯單位級別的復(fù)用,不是源代碼的拷貝粘貼)
通常解決復(fù)雜性有兩種方式
分解:人們面對復(fù)雜性有一個常見的做法:即分而治之,將大問題分解為多個小問題,將復(fù)雜問題分解為多個簡單問題。
抽象:更高層次來講,人們處理復(fù)雜性有一個通用的技術(shù),即抽象。由于不能掌握全部的復(fù)雜對象,我們選擇忽視它的非本質(zhì)細(xì)節(jié),而去處理泛化和理想化了的對象模型。
面向?qū)ο蟀舜笤O(shè)計原則
理解隔離變化
從宏觀層面來看,面向?qū)ο蟮臉?gòu)建方式更能適應(yīng)軟件的變化,能將變化所帶來的影響減為最小
各司其職
從微觀層面來看,面向?qū)ο蟮姆绞礁鼜?qiáng)調(diào)各個類的“負(fù)責(zé)”
由于需求變化導(dǎo)致的新增類型不應(yīng)該影響原來類型的實(shí)現(xiàn)——是所謂各負(fù)其責(zé)
對象是什么?
從語言實(shí)現(xiàn)層面來看,對象封裝了代碼和數(shù)據(jù)。
從規(guī)格層面講,對象是一系列可被使用的公共接口。
從概念層面講,對象是某種擁有責(zé)任的抽象。
兩個非常相似的代碼可能設(shè)計模式完全不一樣,兩個相差千里的代碼可能設(shè)計模式是類似的。
設(shè)計原則一:依賴倒置原則(DIP)
高層模塊(穩(wěn)定)不應(yīng)該依賴于低層模塊(變化),二者都應(yīng)該依賴于抽象(穩(wěn)定)。
抽象(穩(wěn)定)不應(yīng)該依賴于實(shí)現(xiàn)細(xì)節(jié)(變化),實(shí)現(xiàn)細(xì)節(jié)應(yīng)該依賴于抽象(穩(wěn)定)。
(實(shí)例代碼中第一種Mainform依賴Line和Rect,這是不好的,第二種Mainform依賴一個抽象Shape,Line和Rect依賴Shape,實(shí)現(xiàn)隔離變化。)
設(shè)計原則二:開放封閉原則(OCP)
對擴(kuò)展開放,對更改封閉。
類模塊應(yīng)該是可拓展的,但是不可修改。
設(shè)計原則三:單一職責(zé)原則(SRP)
一個類應(yīng)該僅有一個引起它變化的原因。
變化的方向隱含著類的責(zé)任。
設(shè)計原則四:Liskov替換原則(LSP)
子類必須能夠替換它們的基類(IS-A)。
繼承表達(dá)類型抽象。
設(shè)計原則五:接口隔離原則(ISP)
不應(yīng)該強(qiáng)迫客戶程序依賴它們不用的方法。
接口應(yīng)該小而完備。(只是子類使用protected,本類使用private,真正有需要就public。)
設(shè)計原則六:優(yōu)先使用對象組合,而不是類繼承
類繼承通常為“白箱復(fù)用”,對象組合通常為“黑箱復(fù)用”。
繼承在某種程度上破壞了封裝性,子類父類耦合度高。
而對象組合則只要求被組合的對象具有良好定義的接口,耦合度低。
設(shè)計原則七:一般層次對封裝的理解是封裝數(shù)據(jù)和代碼,更高層次的理解是封裝變化層,一側(cè)穩(wěn)定,一側(cè)不穩(wěn)定。
封裝變化點(diǎn):
使用封裝來創(chuàng)建對象之間的分界層,讓設(shè)計者可以在分界層的一側(cè)進(jìn)行修改,而不會對另一側(cè)產(chǎn)生不良的影響,從而實(shí)現(xiàn)層次間的松耦合。
設(shè)計原則八:針對接口編程,而不是針對實(shí)現(xiàn)編程
不將變量類型聲明為某個特定的具體類,而是聲明為某個接口。
客戶程序無需獲知對象的具體類型,只需要知道對象所具有的接口。
減少系統(tǒng)類型中各部分的依賴關(guān)系,從而實(shí)現(xiàn)“高內(nèi)聚、松耦合”的類型設(shè)計方案。
(接口標(biāo)準(zhǔn)化)
將設(shè)計原則提升為設(shè)計經(jīng)驗(yàn)
設(shè)計習(xí)語Design Idioms
Design Idioms描述與特定編程語言相關(guān)的低層模式,技巧,慣用法。
設(shè)計模式Design Patterns
Design Patterns主要描述的是“類與相互通信的對象之間的組織關(guān)系,包括它們的角色、職責(zé)、協(xié)作方式等方面。
架構(gòu)模式Architectural Patterns
Architectural Patterns描述系統(tǒng)中與基本結(jié)構(gòu)組織關(guān)系密切的高層模式,包括子系統(tǒng)劃分,職責(zé),以及如何組織它們之間關(guān)系的規(guī)則。
模板方法
GOF-23模板分類
從目的來看:
創(chuàng)建型(Creational)模式:將對象的部分創(chuàng)建工作延遲到子類或者其他對象,從而應(yīng)對需求變化為對象創(chuàng)建時具體類型實(shí)現(xiàn)引來的沖擊。
結(jié)構(gòu)型(Structural)模式:通過類繼承或者對象組合獲得更靈活的結(jié)構(gòu),從而應(yīng)對需求變化為對象的結(jié)構(gòu)帶來的沖擊。
行為型(Behavioral)模式:通過類繼承或者對象組合來劃分類與對象間的職責(zé),從而應(yīng)對需求變化為多個交互的對象帶來的沖擊。
從范圍來看:
類模式處理類與子類的靜態(tài)關(guān)系
對象模式處理對象間的動態(tài)關(guān)系
從封裝變化角度:
組件協(xié)作 單一職責(zé) 對象創(chuàng)建 對象性能 接口隔離
狀態(tài)變化 數(shù)據(jù)結(jié)構(gòu) 行為變化 領(lǐng)域問題
重構(gòu)獲得模式Refactoring to Patterns
面向?qū)ο笤O(shè)計模式是“好的面向?qū)ο笤O(shè)計”,所謂“好的面向?qū)ο笤O(shè)計”指是那些可以滿足“應(yīng)對變化,提高復(fù)用”的設(shè)計。
現(xiàn)代軟件設(shè)計的特征是“需求的頻繁變化”。設(shè)計模式的要點(diǎn)是“尋求變化點(diǎn),然后在變化點(diǎn)處應(yīng)用設(shè)計模式,從而來更好地應(yīng)對需求的變化”。“什么時候、什么地點(diǎn)應(yīng)用設(shè)計模式”比“理解設(shè)計模式結(jié)構(gòu)本身”更為重要。
設(shè)計模式的應(yīng)用不宜先入為主,一上來就使用設(shè)計模式是對設(shè)計模式的最大誤用。沒有一步到位的設(shè)計模式。敏捷軟件開發(fā)實(shí)踐提倡的“Refactoring to Patterns”是目前普遍公認(rèn)的最好的使用設(shè)計模式的方法。
重構(gòu)關(guān)鍵技法
靜態(tài)->動態(tài)(靜態(tài)綁定轉(zhuǎn)動態(tài)綁定)|早綁定->晚綁定|繼承->組合|編譯時依賴->運(yùn)行時依賴|緊耦合->松耦合
1.組件協(xié)作模式:現(xiàn)代軟件專業(yè)分工之后的第一個結(jié)果是“框架與應(yīng)用程序的劃分”,“組件協(xié)作”模式通過晚期綁定,來實(shí)現(xiàn)框架與應(yīng)用程序之間的松耦合,是二者之間協(xié)作時常用的模式。典型模式:Template Method、Strategy、Observer/Event
基類析構(gòu)函數(shù)要寫成虛析構(gòu)函數(shù),如果不寫虛的,主程序delete的時候可能調(diào)用不到子類的析構(gòu)函數(shù)。
早綁定:后寫的調(diào)用先寫的
晚綁定:先寫的調(diào)用后寫的
所有都是穩(wěn)定或者不穩(wěn)定都不適用設(shè)計模式,設(shè)計模式最大的作用就是在變化和穩(wěn)定中間尋找隔離點(diǎn),然后來分離他們,從而來管理變化,生動的例子:把變化的讓它像小兔子一樣關(guān)在籠子里在房間里跳不至于把房間污染掉。
模式定義:定義一個操作中的算法的骨架(穩(wěn)定),而將一些步驟延遲(變化)到子類中。Template Method使得子類可以不改變(復(fù)用)一個算法的結(jié)構(gòu)即可重定義(override重寫)該算法的某些特定步驟。——《設(shè)計模式》GOF
應(yīng)用程序開發(fā)人員使用lib時,由于lib的開發(fā)人員已經(jīng)寫好程序的主流程和部分步驟的具體實(shí)現(xiàn)(這些是相對穩(wěn)定的), 應(yīng)用開發(fā)人員只需對lib的類進(jìn)行繼承,并重寫部分(override)它的成員函數(shù)(推薦為protdected類型,不被外界直接調(diào)用)即可。(晚綁定)。
前提是有穩(wěn)定和不穩(wěn)定的成分,設(shè)計模式才有用武之地。如果全部都穩(wěn)定,或是全部都不穩(wěn)定,那么就不能使用設(shè)計模式。
虛函數(shù)和函數(shù)指針都是晚綁定。
要點(diǎn)總結(jié)
Template Method模式是一種非常基礎(chǔ)性的設(shè)計模式,在面向?qū)ο笙到y(tǒng)中有著大量的應(yīng)用。它用最簡潔的機(jī)制(虛函數(shù)的多態(tài)性)為很多應(yīng)用程序框架提供了靈活的擴(kuò)展點(diǎn),是代碼復(fù)用(二進(jìn)制運(yùn)行時刻的復(fù)用,不是代碼的復(fù)用)方面的基本實(shí)現(xiàn)結(jié)構(gòu)。
除了可以靈活應(yīng)對子步驟的變化外,“不要調(diào)用我,讓我來調(diào)用你”的反向控制結(jié)構(gòu)是Template Method的典型應(yīng)用。
在具體實(shí)現(xiàn)方面,被Template Method調(diào)用的虛方法可以具有實(shí)現(xiàn),也可以沒有任何實(shí)現(xiàn)(抽象方法、純虛方法),但一般推薦將它們設(shè)置為protected方法。
2.策略模式strategy
動機(jī)
在軟件構(gòu)建過程中,某些對象使用的算法可能多種多樣,經(jīng)常改變,如果將這些算法都編碼到對象中,將會使對象變得異常復(fù)雜;而且有時候支持不使用的算法也是一個性能負(fù)擔(dān)。
如何在運(yùn)行時根據(jù)需要透明地更改對象的算法?將算法與對象本身解耦,從而避免上述問題?
模式定義
定義一系列算法,把它們一個個封裝起來,并且使它們可互相替換(變化)。該模式使得算法可獨(dú)立于使用它的客戶程序(穩(wěn)定)而變化(擴(kuò)展,子類化)。————《設(shè)計模式》GOF
(要動態(tài)的看問題,而不只是靜態(tài)的看待問題。加上時間軸,考慮到未來的變化)
使用多態(tài)的變量(類內(nèi)和類外),要用指針。
為解決同一個問題的不同算法設(shè)計一個類,并實(shí)現(xiàn)各種的計算方法,當(dāng)有新的算法時,則擴(kuò)展算法(定義新的類來實(shí)現(xiàn))。不同算法之間可以相互替換(變化)。
如果if...else if...是絕對穩(wěn)定的,那么大部分可以采用strategy method。
優(yōu)點(diǎn):當(dāng)存在一些無用的算法時, 代碼具有良好的本地性,加載的代碼,就是調(diào)用相應(yīng)的哪個實(shí)現(xiàn)方法,但利用if..else if...他們也會加載這些無用的算法到代碼段,影響性能。
3.觀察者模式observer/event
動機(jī)
在軟件構(gòu)建過程中,我們需要為某些對象建立一種“通知依賴關(guān)系”————一個對象(目標(biāo)對象)的狀態(tài)發(fā)生改變,所有的依賴對象(觀察者對象)都將得到通知。如果這樣的依賴關(guān)系過于緊密,將使軟件不能很好地抵御變化。
使用面向?qū)ο蠹夹g(shù),可以將這種依賴關(guān)系弱化,并形成一種穩(wěn)定的依賴關(guān)系。從而實(shí)現(xiàn)軟件體系結(jié)構(gòu)的松耦合。
模式定義:
定義對象間的一種一對多(變化)的依賴關(guān)系,以便當(dāng)一個對象(Subject)的狀態(tài)發(fā)生改變時,所有依賴于它的對象都得到通知并自動更新。————設(shè)計模式GOF
要點(diǎn)總結(jié):
使用面向?qū)ο蟮某橄螅琌bserver模式使得我們可以獨(dú)立地改變目標(biāo)與觀察者,從而使二者之間的依賴關(guān)系達(dá)致松耦合。
目標(biāo)發(fā)送通知時,無需指定觀察者,通知(可以攜帶通知信息作為參數(shù))會自動傳播。
觀察者自己決定是否需要訂閱通知,目標(biāo)對象對此一無所知。
Observer模式是基于事情的UI框架中非常常用的設(shè)計模式,也是MVC模式的一個重要組成部分。
編譯時依賴。違背依賴倒置原則。
c++支持多繼承,最好一個是主基類,其他的都是接口類。
對象間一對多的依賴關(guān)系,一個對象的狀態(tài)變化時,所有依賴于它的對象得到通知,并各自自動更新。目標(biāo)和觀察者獨(dú)立更改,松耦合。目標(biāo)自動發(fā)送通知,無需指定觀察者。
觀察者自己決定是否訂閱通知,不訂閱通知就不用將這個觀察者放入到目標(biāo)雖維護(hù)的對象集合。目標(biāo)無需知道觀察者是否訂閱了通知。單一職責(zé)原則表現(xiàn)突出的模式。
4.裝飾模式decorator
“單一職責(zé)”模式:在軟件組件的設(shè)計中,如果責(zé)任劃分的不清晰,使用繼承得到的結(jié)果往往是隨著需求的變化,子類急劇膨脹,同時充斥著重復(fù)代碼,這時候的關(guān)鍵是劃清責(zé)任。典型模式:Decorator、Bridge。
動機(jī)
在某些情況下我們可能會“過度地使用繼承來擴(kuò)展對象的功能”,由于繼承為類型引入的靜態(tài)特質(zhì),使得這種擴(kuò)展方式缺乏靈活性;并且隨著子類的增多(擴(kuò)展功能的增多),各種子類的組合(擴(kuò)展功能的組合)會導(dǎo)致更多子類的膨脹。
何如使“對象功能的擴(kuò)展”能夠根據(jù)需要來動態(tài)地實(shí)現(xiàn)?同時避免“擴(kuò)展功能的增多”帶來的子類膨脹問題?從而使得任何“功能擴(kuò)展變化”所導(dǎo)致的影響降為最低?
模式定義:
動態(tài)(組合)地給一個對象增加一些額外的職責(zé)。就增加功能而言,Decorator模式比生成子類(繼承)更為靈活(消除重復(fù)代碼&減少子類個數(shù))。————《設(shè)計模式》GOF
要點(diǎn)總結(jié):
通過采用組合而非繼承的手法,Decorator模式實(shí)現(xiàn)了在運(yùn)行時動態(tài)擴(kuò)展對象功能的能力,而且可以根據(jù)需要擴(kuò)展多個功能。避免了使用繼承帶來的“靈活性差”和“多子類衍生問題”。
Decorator類在接口上表現(xiàn)為is-a Component的繼承關(guān)系,即Decorator類繼承了Component類所具有的接口。但在實(shí)現(xiàn)上又表現(xiàn)為has-a Component的組合關(guān)系,即Decorator類又使用了另外一個Component類。
Decorator模式的目的并非解決“多子類衍生的多繼承”問題,Decorator模式應(yīng)用的要點(diǎn)在于解決“主體類在多個方向上的擴(kuò)展功能”——是為“裝飾”的含義。
組合-->繼承;編譯時-->運(yùn)行時的多態(tài);使用時,編譯時裝飾-->運(yùn)行時裝飾。
5.橋模式bridge
模式定義:
將抽象部分(業(yè)務(wù)功能)與實(shí)現(xiàn)部分(平臺實(shí)現(xiàn))分離,使它們都可以獨(dú)立地變化。——《設(shè)計模式》GOF
要點(diǎn)總結(jié):
Bridge模式使用“對象間的組合關(guān)系”解耦了抽象和實(shí)現(xiàn)之間固有的綁定關(guān)系,使得抽象和實(shí)現(xiàn)可以沿著各自的維度來變化。所謂抽象和實(shí)現(xiàn)沿著各自維度的變化,即“子類化”它們。
Bridge模式有時候類似于多繼承方案,但是多繼承方案往往違背單一職責(zé)原則(即一個類只有一個變化的原因),復(fù)用性比較差。Bridge模式是比多繼承方案更好的解決方法。
Bridge模式的應(yīng)用一般在“兩個非常強(qiáng)的變化維度”,有時一個類也有多于兩個的變化維度,這時可以使用Bridge的擴(kuò)展模式。(如果有三個變化維度,把其他變化維度的合在一起打包成一個基類,用三個抽象的指針指向它。)