Boolan c++設計模式第三周筆記

(一)“對象性能”模式

面向對象很好的解決了“抽象”的問題,但是必不可免地要付出一定的代價。對于通常情況來講,面向對象的成本大都可以忽略不計。但是某些情況,面向對象所帶來的成本必須謹慎處理。

典型模式:Singleton、Flyweight。

1、Singleton單件模式

Singleton單件模式定義:保證一個類僅有一個實例,并提供一個該實例的全局訪問點。

Singleton單件模式動機:在軟件系統中,經常有這樣一個特殊的類,必須保證它們在系統中只存在一個示例,才能確保他們的邏輯正確性、以及良好的效率。

如何繞過常規的構造器,提供一種機制來保證一個類只有一個實例?這個應該類設計者的責任,而不是使用者的責任。

優點:減少了時間和空間的開銷(new實例的開銷);提高了封裝性,使得外部不易改動實例。

缺點:懶漢式是以時間換空間的方式;餓漢式是以空間換時間的方式。

Singleton單件模式的結構如圖1所示。



? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?圖1

總結:

(1)Singleton模式中的實例構造器可以設置為protected以允許子類派生。

(2)Singleton模式一般不要支持拷貝構造函數和Clone接口,因為這有可能會導致多個對象實例,與Singleton模式的初衷相違背。

(3)實現多線程環境下安全的Singleton,注意對雙檢查鎖的正確實現。

雙檢查鎖,在lock的前后判斷m_instance是否為空。因為可能多個線程都走進到m_instance==nullptr分支,所以之后每個線程在獲得鎖之后要再次判斷m_instance==nullptr,來確保m_instance不會被重復實例化。但可能出現內存讀寫reorder問題,在經過編譯器優化后,實例化Singleton可能不是按照分配空間、構造和地址賦值給指針的順序進行的,而是按照分配空間、指針賦值、構造這三個步驟,當一個線程執行到指針賦值后,如果有另一個線程進來判斷m_instance指針不為空,直接返回m_instance,并直接使用這個指針,那就會發生錯誤,因為第一個線程還沒有執行構造器,所以這時雙檢查鎖也就是失效了。解決辦法,將變量聲明為volatile防止編譯器優化代碼。

2、Flyweight享元模式

Flyweight享元模式定義:運用共享技術有效地支持大量的細粒度對象。

Flyweight享元模式動機:在軟件系統中采用純粹對象方案的問題 在于大量細粒度的對象會很快充斥在系統中,從而帶來很高的運行時代價——主要指內存需求方面的代價。

Flyweight享元模式的結構如圖2所示。



? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?如2

總結:

(1)面向對象很好的解決了抽相性的問題,但是作為一個運行在機器中的程序實體,我們需要考慮對象的代價問題。Flyweight主要解決面向的代價問題,一般不觸及面向對象的抽象性問題。

(2)Flyweight采用對象共享的做法來降低系統中的對象的個數,從而降低細粒度對象給系統帶來的內存壓力。在具體實現方面,要注意對像狀態的處理。

(3)對象的數量太大,從而導致對像內存開銷加大——什么樣的數量才算大?這需要我們仔細根據具體應用情況進行評估,而不能憑空臆斷。

(二)“狀態變化”模式

在組建構建過程中,某些對象的狀態經常面臨變化,“狀態變化”模式可以使變化進行有效的管理,同時又維持高層模塊的穩定。

典型模式:State、Memento。

3、State狀態模式

State狀態模式定義:允許一個對象在其內部狀態改變是改變它的行為。從而使對像看起來似乎修改其行為。

State狀態模式動機:在軟件構建過程中,某些對象的狀態如果改變,其行為也會隨之而發生變化,比如文檔處于只讀狀態,其支持的行為和讀寫狀態支持的行為就可能會完全不同。

如何在運行時根據對象的狀態來透明地更改對象的行為?而不會為對象操作和狀態轉化之間引入緊耦合?

優點

(1)狀態模式將與特定狀態相關的行為局部化,并且將不同狀態的行為分割開來。

(2)所有狀態相關的代碼都存在于某個ConcereteState中,所以通過定義新的子類很容易地增加新的狀態和轉換。

(3)狀態模式通過把各種狀態轉移邏輯分不到State的子類之間,來減少相互間的依賴。

缺點:導致較多的ConcreteState子類。

State狀態模式的結構如圖3所示。



? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?圖3

總結:

(1)通過擴展子類,來添加進狀態,解決狀態轉化問題,當有if...else時,可以轉換成此方法。

(2)State模式將所有與一個特定狀態相關的行為都放入一個State的子類對象中,在對像狀態切換時, 切換相應的對象;但同時維持State的接口,這樣實現了具體操作與狀態轉換之間的解耦。

(3)為不同的狀態引入不同的對象使得狀態轉換變得更加明確,而且可以保證不會出現狀態不一致的情況,因為轉換是原子性的——即要么徹底轉換過來,要么不轉換。

(4)如果State對象沒有實例變量,那么各個上下文可以共享同一個State對象,從而節省對象開銷。

4、Memento備忘錄

Memento備忘錄模式定義:在不破壞封裝性的前提下,不活一個對象的內部狀態,并在該對像之外保存這個狀態。這樣以后就可以將該對像恢復到原想保存的狀態。

Memento備忘錄模式動機:在軟件構建過程中,某些對象的狀態在轉會過程中,可能由于某種需求,要求程序能夠回溯到對像之前處于某個點時的狀態。如果使用一些公有借口來讓其它對象得到對象的狀態,便會暴露對象的實現細節。

如何實現對象狀態的良好保存與恢復?但同時又不會因此而破壞對象本身的封裝性。

Memento備忘錄模式的結構如圖4所示。



? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?圖4

總結:

(1)在狀態轉化時,還原到某一個狀態,用原發器創建備忘錄,保存。

(2)備忘錄(Memento)存儲原發器(Originator)對象的內部狀態,在需要時恢復原發器的狀態。

(3)Memento模式的核心是信息隱藏,即Originator需要向外接隱藏信息,保持其封裝性。但同時又需要將其狀態保持到外界(Memento)

(4)由于現代語言運行時(如C#、java等)都具有相當的對象序列化支持,因此往往采用效率較高、又較容易正確實現的序列化方案來實現Memento模式。

(三)“數據結構”模式

常常有一些組建在內部具有特定的數據結構,如果讓客戶程序依賴這些特定的數據結構,將極大的破壞組件的復用。這時候,將這些數據結構封裝在內部,在外部提供統一的接口,來實現與特定數據結構無關的訪問,是一種行之有效的解決方案。

典型模式:Composite、Iterator、Chain of Responsibility。

5、Composite組合模式

Composite組合模式定義:將對象組合成樹形結構以表示“部分-整體”的層級結構。Compisite使得用戶對單個對象和組合對象的使用具有一致性(穩定)。

Composite組合模式動機:軟件在某些情況下,客戶代碼過多地依賴于對像容器復雜的內部實現結構,對象容器內部實現結構(而非抽象接口)的變化將因其客戶代碼的頻繁變化,帶來了代碼的維護性、擴展性等弊端。

如何將“客戶代碼與復雜的對象容器結構”解耦?讓對象容器自己來實現自身的復雜結構,從而使得客戶代碼就像處理簡單對象一樣來處理復雜的對象容器?

Composite組合模式的結構如圖5所示。



? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?圖5

總結:

(1)Composite模式采用樹形結構來實現普遍存在的對象容器,從而將“一對多”的關系轉化為“一對一”的關系,使得客戶代碼可以一致地(復用)處理對象和對象容器,無需關心處理的是單個對象還是組合的對象容器。

(2)將“客戶代碼與復雜的對象容器結構”解耦是Composite的核心思想,解耦之后,客戶代碼將與純粹的抽象接口——而非對像容器的內部實現結構——發生依賴,從而更能“應對變化”。

(3)Composite模式在具體實現中,可以讓父對象中的子對象反向追溯;如果父對象有頻繁的遍歷需求,可使用緩存技巧來改善效率。

6、Iterator迭代器

Iterator迭代器模式定義:提供一種方法順序訪問一個聚合對象中的各個元素,而又不暴露(隔離變化,穩定)該對象的內部表示。

Iterator迭代器模式動機:在軟件構建過程中,集合對象內部結構常常變化各異。但對于這些集合對象,我們希望在不暴露其內部結構的同時,可以讓外部客戶代碼透明的訪問其中包含的元素;同時這種“透明遍歷”也為“同一種算法在多種集合對象上進行操作”提供了可能。

使用面向對象技術將這種遍歷機制抽象為“迭代器對象”為“因對變化中的集合對象”提供了一種優雅的方式。

Iterator迭代器模式的結構如圖6所示。



? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? 圖6

總結:

(1)迭代抽象:訪問一個聚合對象的內容而無需暴露他的內部表示。

(2)迭代多態:為遍歷不同的集合結構提供一個統一的接口,從而支持同樣的算法在不同的集合結構上進行操作。

(3)迭代器健壯性考慮:遍歷的同時更改迭代器所在的集合結構,會導致問題。

7、Chain of Responsibility職責鏈

Chain of Responsibility職責鏈模式定義:使多個對像都有機會處理請求,從而避免請求的發送者和接收者之間的耦合關系。將這些對像連成一條鏈,并沿著這條鏈傳遞請求,直到有一個對象處理它為止。

Chain of Responsibility職責鏈模式動機:在軟件構建的過程中,一個請求可能被多個對象處理,但是每個請求在運行時只能有一個接受者,如果顯示指定,將必不可少的帶來請求發送者與接受者的耦合。

如何使請求的發送者不需要指定具體的接受者?讓請求的接受者自己在運行時決定來處理請求,從而使兩者解耦。

Chain of Responsibility職責鏈模式的結構如圖7所示。



? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? 圖7

總結:

(1)Chain of Responsibility模式的應用場合在于“一個請求可能有多個接受者,但是最后真正接受者只有一個”,這時候請求發送者與接受者的耦合可能出現“變化脆弱”的癥狀,職責鏈的目的就是將二者解耦,從而更好的應對變化。

(2)應用了Chain of Responsibility模式后,對象的職責分派將更具靈活性。我們可以在運行時動態添加/修改請求的處理指責。

(3)如果請求傳遞到職責鏈的末尾仍得不到處理,應該有一個合理的缺省機制。這也是每一個接受者對象的責任,而不是發出請求的對象的責任。

(四)“行為變化”模式

在組建的構建過程中,組建行為的變化經常導致組建本身劇烈的變化。“行為變化”模式將組建的行為和組建本身進行解耦,從而主持組件的變化,實現兩者之間的松耦合。

典型模式:Command、Visitor。

8、Command命令模式

Command命令模式模式定義:將一個請求(行為)封裝為對象,從而使你可用不同的請求,對客戶進行參數化;對請求排隊或記錄請求日志以及支持可撤銷的操作。

Command命令模式動機:在軟件構建構成中,“行為請求者”與“行為實現者”通常呈現一種“緊耦合”。但在某些場合——比如需要對行為進行“記錄、撤銷(undo)、事務”等處理,這種無法抵御變化的緊耦合是不合適的。

在這種情況下,如何將“行為請求者”與“行為實現者”解耦?將一組行為抽象為對象,可以實現二者之間的松耦合。

Command命令模式的結構如圖8所示。



? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?圖8

總結:

(1)Command模式的根本目的在于“行為請求者”與“行為實現者”解耦,在面向對象的語言中,常見的實現手段是“將行為抽象為對象”

(2)實現Command接口的具體命令對象ConcreteCommand有時候根據需要可能會保存一些額外的狀態信息。通過使用Composite模式,可以將多個“命令”封裝為一個“符合命令”MacroCommand

(3)Command模式與C++中的函數對像有些類似。但兩者定義行為接口的規范有所區別:Command以面向對象中的“接口-實現”來定義行為接口規范,更嚴格,但有性能損失;C++函數對象以函數簽名來定義行為接口規范,更靈活,性能能高。

9、Visitor訪問器

Visitor訪問器模式定義:表示一個作用與某對像結構中的各元素的操作。使得可以在不改變(穩定)各元素的類的前提下定義(擴展)作用于這些元素的新操作(變化)。

Visitor訪問器模式動機:在軟件構建的過程中,由于需求的改變,某些類層次結構中常常需要增加新的行為(方法)。如果直接在類中做這樣的更改,將會給子類帶來很繁重的變更負擔,甚至破壞原有設計。

如何在不更改類層次結構的前提下,在運行時根據需要透明地為類層次結構上的各個類動態添加新的操作,從而避免上述問題?

Visitor訪問器模式的結構如圖9所示。



? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? 圖9

總結:

(1)Vistor模式通過所謂的雙重分發(double dispatch)來實現在不更改(不添加新的操作-編譯時)Element類層次結構的前提下,在運行時透明地為類層次結構上的各個類動態添加新的操作(支持變化)。

(2)所謂雙重分發即Vistor模式中間包括了兩個多態分發(注意其中的多態機制):第一個accept方法的多態解析;第二個為visitElementX方法的多態辨析。

(3)Visitor模式最大的缺點在于擴展類層次結構(增添新的Element子類),會導致Visitor類的改變。因此Visitor模式適用于“Element類層次結構穩定,而其中的操作卻進場面臨頻繁改動”。

(五)“領域規則”模式

在特定領域內,某些變化雖然頻繁,但可以抽象為某種規則。這時候,結合特定領域,將問題抽象為語法規則,從而給出該領域下的一般性解決方案。

典型模式:Interpreter。

10、Interpreter解析器

Interpreter解析器模式定義:給定一個語言,定義它的文法的一種表示,并定義一種解釋器,這個解釋器使用該表示來解釋語言中的句子。

Interpreter解析器模式動機:在軟件構建過程中,如果某一特定領域的問題比較復雜,類似的結構不斷的重復出現,如果使用普通的變成方式來實現將面臨非常頻繁的變化。

在這種情況下,將特定領域的問題表達為某種語法規則下的句子,然后構建一個解析器來解釋這樣的句子,從而達到解決問題的目的。

Interpreter解析器模式的結構如圖10所示。



? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?圖10

總結:

(1)Interpreter模式的應用場合是Interpreter模式應用中的難點,只有滿足“業務規則頻繁變化,且類似的結構不斷重復出現,并且容易抽象為語法規則的問題”才適合使用Interpreter模式。

(2)使用Interpreter模式來表示文法規則,從而可以使用面向對象技巧來方便地“擴展”文法。

(3)Interpreter模式比較適合簡單的文法表示,對于復雜的文法表示,Interpreter模式會產生比較大的類層次結構,需要求助于語法分析生成器這樣的標準工具。

設計模式總結

一個目標:管理變化,提高復用。

兩種手段:分解,抽象。

八大原則:依賴倒置原則(DIP)

開放封閉原則(OCP)

單一職責原則(SRP)

Liskov替換原則(LSP)

接口隔離原則(ISP)

優先使用對象組合,而不是類繼承

封裝變化點

針對接口編程,而不是針對實現編程



? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? 八大原則關系

重構方法:靜態變為動態,早綁定變為晚綁定,繼承變為組合,編譯時依賴變為運行時依賴,緊耦合變為松耦合。

C++對象模型:


最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容