设计模式(五)行为型模式
前言
在上一篇結構型模式中,我們以功能為基本單位,研究了一些設計模式,用于實現功能轉換、功能組合、功能封裝等目的。
我們知道,面向對象編程有兩個核心元素:對象、對象間通信協作。從面向對象的角度看,任何系統和功能,都是由一個個對象,相互分工合作實現的。推而廣之,很多系統也都是這樣組織和運行的。
本章的設計模式,列舉了通用場景下常用功能機制的經典實現方法,講解了經典實現中是如何高效組織對象、控制對象協作交互的,具有很好的參考價值。
責任鏈模式
示例:https://www.runoob.com/design-pattern/chain-of-responsibility-pattern.html
原理說明
責任鏈模式,就是把程序處理對象前后排列起來,形成一條處理線。處理線上需要被處理的信息,在處理線上向下傳遞,任何一個節點都可以隨時中斷傳遞。
使用場景
GUI系統中的事件傳遞機制(在Javascript中叫做事件冒泡),是責任鏈模式最典型的應用之一。
當某一事件發生時,最頂層GUI對象會首先收到事件,但是它先不處理,而是依次交給命中的子GUI對象處理。當子GUI對象返回為False時,表示事件未被接收,此時父GUI對象才真正對發生的事件進行業務處理。
可以看出,事件傳遞機制,是一種增強版的責任鏈模式,它的節點處理權,經歷了向下和向上的雙向傳遞過程。
總結:當項目中一個數據對象,需要被多個處理對象進行處理時,可以將處理對象鏈接起來,然后把數據對象傳遞給頭節點,隨著處理的進行,數據對象的處理權會在處理鏈中流動,從而完成整個處理過程。
使用須知
責任鏈模式結構適用于需求固定的場景,用于實現簡單高效的處理機制。假如需求不斷變化,而且功能很復雜,那么用責任鏈模式很可能就無法勝任了,需要采用新的高復雜度的設計。例如,如果想要數據對象在所有處理對象中根據狀態來實現跳轉,可以選擇使用狀態機等其他方案來實現。
本質
責任鏈模式從本質上說,是一種簡單的線性對象組織方式,優點是簡單高效。它將處理對象封裝集合在一起,實現對數據的集中處理,實現了處理對象的高內聚,降低了處理對象和數據對象的耦合。
命令模式
示例:https://www.runoob.com/design-pattern/command-pattern.html
使用場景
想要實現撤銷、重做、事務等功能,可以使用此設計模式。通常在編輯器、數據庫中有此類功能需求。
原理說明
命令也就是請求,或者叫調用。命令模式要求將請求參數和請求相關的方法封裝在一起。
請求對象中封裝了實現“撤銷”、“重做”、“事務”功能所需要的所有信息,實現了關聯信息的高內聚,所以可以實現我們想要的功能。
例如,可以在請求對象中保存修改之前的值、修改之后的值。利用修改之前的值,可以實現“撤銷”功能;利用修改之后的值,可以實現“重做”功能。如果將所有請求對象都記錄下來,并按照先后順序排列起來,形成“撤銷重做”堆棧,這樣就可以實現連續的“撤銷”、“重做”。“事務”則是“撤銷”與“重做”的結合體,正常執行流程等同于“重做”,發生錯誤需要回滾,等同于“撤銷”。
如果不采用這種方式,會導致實現這些功能的信息,分散在源碼中多個地方,或者已經丟失,沒有保存,就無法實現“撤銷”、“重做”、“事務”功能。
同時,實現請求參數高內聚,也可以很方便地將它們保存到磁盤上,保存到文件的過程叫做“序列化”,從文件中讀取的過程叫“反序列化”。這里的序列指的就是二進制流。
Qt中與命令模式相關的部分是:Undo Framework,里面有示例項目,不熟悉的同學可以抽點時間看一看。
本質
命令模式的本質是,將請求相關的信息和操作封裝在一起,實現信息的高度內聚,可以方便地實現“撤銷”、“重做”、“事務”等功能。
解釋器模式
示例:https://www.runoob.com/design-pattern/interpreter-pattern.html
原理說明
顧名思義,解釋器模式是用來實現解釋器的。
解釋器是這樣一個程序:解釋器以符合語法的文本為輸入,解釋輸入內容,完成一定的計算功能。文本可以在程序運行時動態加載,動態解釋、動態執行。
使用場景
實現簡單的解釋器:命令行程序,如ping命令、cd命令等;
實現復雜的解釋器:腳本語言解釋器,如python,lua,javascript;計算器。
我們知道,在GUI圖形用戶界面被發明之前,人類和程序之間的交互是通過敲命令行實現的,缺點是使用難度較大,門檻較高。
在GUI發明以后,交互更加友好,電腦更加易于使用了,所以也更加普及了。
但是GUI交互的缺點在于,不夠靈活,對參數的控制粒度不夠細致。例如,現在大多數開發者都使用集成開發環境來開發軟件,一般情況下都使用默認參數,比較方便。但是如果你想要更改某些編譯選項,可能還是需要直接修改底層的編譯命令。命令相對于GUI元素更加靈活,過于靈活的地方用GUI比較難于實現,例如組合、遞歸、跳轉等等。在這些場景下,使用解釋器是非常合適的。但是通常情況下,這個模式并不常用。
本質
解釋器模式的本質是,它提出了一種基于文本的、比GUI更加靈活的對外提供功能服務的方式。為了實現這種交互功能,需要以命令文本為參數,把解釋、執行的過程封裝在解釋器內部。
迭代器模式
示例:https://www.runoob.com/design-pattern/iterator-pattern.html
使用場景
在需要多次遍歷同一個數據集合的時候,為了少些一些for,或者想要把遍歷過程封裝起來,降低耦合,就可以使用迭代器模式。這個模式非常常用。
原理說明
迭代器就是一個專門用來遍歷數組的類。它只需要實現兩個接口:hasNext()、next()。
hasNext()接口用于控制循環何時停止;next()接口用于取出當前位置的數據元素,并將遍歷指針指向下一個元素。
當然,構造迭代器對象的時候,需要將數據集合傳遞給迭代器,讓迭代器知道要遍歷哪些數據。
原本需要用for循環來遍歷的代碼,現在通過封裝,提取出了“遍歷”這一功能所需要的必要信息,定義了兩個接口,把不必要暴露的信息封裝在了迭代器中,妥妥的實現了解耦。
本質
迭代器模式的本質是,為了更好地實現遍歷功能,新建一個迭代器類專門封裝底層的遍歷過程,暴露兩個簡單的接口,實現遍歷功能使用者和底層遍歷過程的解耦。
中介者模式
示例:https://www.runoob.com/design-pattern/mediator-pattern.html
原理說明
中介者模式是指,在原本直接通信的對象之間,添加一個通信中間層,使對象間通信變為間接通信,降低對象間的耦合。
此模式和代理模式基本思想上是一致的。二者的區別是:代理模式是通過加一個中間層,來實現兩個原本很難交互的功能主體,實現順暢交互;中介者模式是為了降低對象間通信時的耦合而提出的,為的是提高代碼的可維護性。
使用場景
比較大的項目中會用到,一般存在于某些框架中。因為大的項目中對象繁多,通信也比較復雜,適合使用中介者模式。
在大的項目中,一般會有一個全局的通信管理器,任何對象都可以使用通信管理器提供的接口,將自己注冊為某一個具有唯一ID消息的發送者和接收者。這樣發送者只需要發送消息,不需要管誰來接收,不需要擁有發送者的實例指針,發出消息后,已注冊的接收者都會收到消息。接收者不需要管信號是誰發的,即不需要擁有發送者的實例指針。
所以,中介者模式也可以叫“通信中介模式”。
本質
中介者模式的本質是:對于信號接收者來說,中介者模式將信號發送者封裝了起來;對信號發送者來說,中介者模式將信號接收者封裝了起來,實現了通信雙方的互相解耦。這是一種優化對象間通信協作的一種設計模式。
備忘錄模式
示例:https://www.runoob.com/design-pattern/memento-pattern.html
使用場景
這個模式和狀態存檔功能是綁定在一起的。為了在程序中實現狀態存檔功能,可以使用備忘錄模式。
使用說明
原例子中有三個類,個人覺得沒有必要,這里我們簡化成兩個類,即備忘錄模式中有兩個類:狀態對象類和狀態對象管理類。
狀態對象類是狀態字段是集合,并提供了存取接口;狀態對象管理類負責組織和保存狀態對象。當然實際實現中可以根據需求增加類,配合使用,完成狀態保存恢復。
本質
備忘錄模式專注于實現程序中狀態的存檔功能,它封裝了狀態的保存與恢復過程,使用者無需關心具體細節。
觀察者模式
使用場景
當一個對象會影響到其他多個對象時,即當對象間存在一對多關系時,使用觀察者模式。
一般應用于單向通知的場景,如GUI中鼠標事件、按鍵事件、窗口事件通知。使用Qt中的信號槽機制可以實現此模式。
使用說明
“一”是指發生變化的那個對象,“多”是指需要獲取此變化通知的對象組。其中,變化消息是單向地由“一”到“多”傳遞的。如果不是單向的或者對象間不是一對多的關系,更加復雜,就需要重新思考其他對象間通信模型。
如果不使用此模式,可能會導致觀察者不能動態增加或刪除;可能會造成發送者的業務代碼和接收者的響應代碼混在一起,耦合嚴重。
使用此模式,需要為觀察者設計一個基類,并設計一個接收通知的接口,所有觀察者需要實現通知接口;所有觀察者指針可以保存在隊列中,實現動態增刪。
本質
觀察者模式本質是為了實現對象的“一對多”單向通信,是一種通信模式。它將消息發送者和接收者獨立封裝,實現了二者的解耦。
狀態模式
使用場景
狀態模式用于實現狀態機。
如果一個程序功能中存在某些狀態,在一定情況下,這些狀態可以互相轉換,并且在轉換前后需要作出對應的操作,這種情況下使用狀態機來實現就非常合適。
使用說明
如果不使用狀態機(狀態模式),一般的實現方法是使用一連串的if-else,或者使用長長的switch-case來實現。這樣做的缺點,一方面狀態判斷不夠高效,另一方面是業務代碼集中在一塊,不好維護。
使用狀態機,每個狀態都是一個類,相關的業務代碼分布到各自的狀態類中,能夠實現不同的狀態及與狀態相關的業務代碼解耦。同時某個狀態和下一個狀態是關聯好的,在狀態切換時,效率更高,不需要執行長長的判斷。
Qt中已實現狀態機框架,The State Machine Framework,在此框架下,我們可以更加專注于業務實現,而不是狀態機本身的技術細節。
本質
狀態模式的本質是將不同的狀態封裝成不同的類,實現狀態與狀態間解耦。
空對象模式
示例:https://www.runoob.com/design-pattern/null-object-pattern.html
使用場景
使用基類保存子類對象通常有兩種做法:
- 用基類指針保存子類對象;
- 用基類對象保存子類對象。
第一種方法用指針是基本方法,但是指針用起來要非常小心,要考慮內存釋放的問題。此時空對象就可以用空指針表示。
第二種方法用基類對象保存子類對象,這種方法使用起來相對省心,不用與指針打交道,使用者不用直接管理內存。例如Qt中的Qt XML C++ Classes類的設計就是采用這種方式設計的。這種情況下,因為不使用指針,就需要使用空對象來代替空指針。
使用說明
可以仿造Qt XML中的類進行設計。一般需要提供isNull()接口,對象類型轉換接口等。
本質
空對象模式的本質是:使用對象化的形式,封裝指針和內存操作,針對實現對象的保存,實現0指針設計,一般用在子類父類的設計中。
策略模式
使用場景
策略模式和橋接模式類似,用于實現功能切換與組合。二者區別在于,策略模式專注于一個功能的不同實現方式;橋接模式專注于多個功能之間的組合。
使用說明
將功能抽象成單獨的類,功能切換只需要切換不同的功能子類即可,同一個功能需要實現同一個功能接口。
本質
策略模式的本質是,保持功能接口不變,將不同的功能實現策略封裝成子類,可以實現功能策略的切換。
模板模式
示例:https://www.runoob.com/design-pattern/template-pattern.html
使用說明
模板模式應該是我們最熟悉的。
這里的模板就是接口類,接口類定義了使用者和功能提供者之間交互的函數列表。子類負責功能的具體實現。
本質
模板模式的本質是:利用C++的虛函數特性,使用接口將具體實現封裝了起來,實現了功能對外接口和功能具體實現的解耦。
訪問者模式
示例:https://www.runoob.com/design-pattern/visitor-pattern.html
使用場景
訪問者模式用于將數據結構與數據操作相分離。
使用說明
訪問者模式和迭代器模式類似。迭代器模式一般用來遍歷數組,所以沒有把for封裝起來。而訪問者模式可以遍歷一切類型的數據結構,具體的遍歷過程被封裝在接收者內部。同時,對每一個遍歷得到的數組元素的操作,被封裝在訪問者內部。每一種對元素不同的操作,都需要新建一個訪問者類。
接收者需要實現accept()接口,訪問者需要實現visit()接口。
本質
訪問者模式的本質是,封裝了數據遍歷過程,同時也封裝了數據操作過程,以一種高度封裝的方式,實現了數據遍歷操作功能。
結語
每種設計模式都有使用場景,都有優點和缺點。隨著需求的改變,任何一種設計模式可能都將不再適用。
總結
以上是生活随笔為你收集整理的设计模式(五)行为型模式的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 浦发美团点评信用卡超市能刷吗
- 下一篇: asp.net ajax控件工具集 Au