设计模式实用讲解
隨著代碼寫的越來越多,關于設計模式的學習和思考也日益增多。
那么究竟什么是設計模式呢?
有一件事情讓我印象非常深刻:
我和一個工作中的小伙伴‘老何’討論設計模式的一種——命令模式,其含義是:命令模式:將“請求”封裝成對象,以便使用不同的請求,隊列或者日志來參數化其他對象。
我一直徘徊在代碼的邊緣,總感覺把握不住這個模式的奧妙。于是我就一直在疑惑:是不是我給想要的類實現統一的接口,并且把每一個要調用的類實例化對象并按順序排列到隊列中,然后通過調用統一接口的方式實現命令模式。
老何聽了之后覺得非常的好笑:“你這真是把書讀死了,難道我不實現接口就不能實現命令模式嗎?命令模式乃至所有的設計模式其實是一種思想,而不是怎么寫類,寫繼承,寫封裝。”
我突然感覺醍醐灌頂,我太在意如何通過代碼實現設計模式,而忘記了設計模式本身的思想。我立馬開始反思:“那這樣說,我們之前寫的lua代碼把動作放到函數中,并做成列表去循環調用,是不是也可以算得上命令模式?”
老何嘿嘿一笑:孺子可教也。
那么,這里就來總結一下一些常用的設計模式,這里我只總結使用場景和設計圖,明白了原理,代碼其實很好寫:
1.單例:
定義:確保一個類只有一個實例,并提供一個全局訪問點。
一般實現方式:
public sealed class Singleton {private static Singleton instance = null;private Singleton() { }public static Singleton Instance{get{if (instance == null){instance = new Singleton();}return instance;}} }更詳細的實現方法可以看:C#實現單例模式的幾種方法 - xiaohanxixi - 博客園
?
2.策略模式:
定義:定義了算法族,分別封裝起來,讓他們之間可以互相替換,此模式讓算法的變化獨立于使用算法的客戶。
UML圖:
其中,Context是上下文,用一個ConcreteStrategy來配置,維護一個對Strategy對象的引用;Strategy是策略類,用于定義所有支持算法的公共接口;ConcreteStrategy是具體策略類,封裝了具體的算法或行為,繼承于Strategy。
使用場景:當一個系統中有很多實現的方式,并且這些方式是可以抽象成一系列算法時,如果不用策略模式來維護那么就要用大量的if else代碼來處理。
舉例: 比如出行方式,可以是坐公交,也可以是開車,也可以是坐地鐵。游戲開發中最直接的例子就是出牌算法:對于棋牌類游戲來說,玩家的牌值都是一樣的,但是斗地主和雙扣的玩法是完全不一樣的,那么把不同的玩法封裝成獨立的算法,這里用的就是策略模式。再舉一個簡單的例子,在所有ui界面彈出時,可以是從大到小展示,也可以是從小到大展示,還可以是從上到下展示,這些都可以使用策略模式來優化代碼。
設計原則:
1.找出應用中可能需要變化之處,把他們獨立出來,不要和那些不需要變化的代碼混在一起出現。
2.針對接口編程,而不是針對實現編程。
3.多用組合,少用繼承。
?
3.觀察者模式:
定義:定義了對象之間的一對多依賴,這樣一來,當一個對象改變狀態時,它的所有依賴者都會受到通知,并自動更新。
UML圖:
?使用場景:當主題處于一對多的關系時,主題發生修改,所關注的對象根據變化都要做出動作。
舉例:觀察者模式是mvc,mvvm等設計架構的核心之一,得以讓界面和數據代碼解耦,讓整個游戲代碼更加清晰。cocos2dx中的lua實現方式(DataCenter)其實非常棒,用了lua的元表和元方法,代碼簡潔清晰,其原理是把每個數據都作為一個觀察主題,觀察者把自己的回調函數加到主題的觀察列表中,當數據主題發生改變就依次調用觀察列表中的回調函數。其實觀察者還有一個特性,就是他擁有牢靠的數據,在其他界面需要數據時,可以向主題索取。后面我會再寫一個設計架構的博文,詳細講觀察者模式。
設計原則:
4.為了交互對象之間的松耦合而努力。
?
4.裝飾者模式:
定義:動態的將責任附加到對象上。若要擴展功能,裝飾者提供了比繼承更有彈性的替代方案。
UML圖:
使用場景:
- 當需要給一個現有類添加附加職責,而又不能采用生成子類的方法進行擴充時。例如,該類被隱藏或者該類是終極類或者采用繼承方式會產生大量的子類。
- 當需要通過對現有的一組基本功能進行排列組合而產生非常多的功能時,采用繼承關系很難實現,而采用裝飾器模式卻很好實現。
- 當對象的功能要求可以動態地添加,也可以再動態地撤銷時。
舉例:我想了很久在以前的代碼中似乎沒有遇到過裝飾者模式的使用,但在最近接觸了我們creator的框架時,發現pbfm預制體管理類在某種程度上來說就是使用的裝飾者模式,為獲取到的ui節點添加新的職責。
設計原則:
5.對擴展開放,對修改關閉。
?
5.工廠方法和抽象工廠模式:
定義:
工廠方法模式:定義了一個創建對象的接口,但由子類決定要實例化的類是哪一個。工廠方法讓類把實例化推遲到子類。
抽象工廠模式:提供了一個接口,用于創建相關或依賴對象的家族,而不需要明確指出具體類。
UML圖:
?
使用場景:
- 客戶只知道創建產品的工廠名,而不知道具體的產品名。如 TCL 電視工廠、海信電視工廠等。
- 創建對象的任務由多個具體子工廠中的某一個完成,而抽象工廠只提供創建產品的接口。
- 客戶不關心創建產品的細節,只關心產品的品牌
舉例:比如有一個需求,邊茶兩端生成ui彈窗的底圖不一樣,一個黑的一個白的,這時候就可以構建ui抽象類擁有createBg函數,邊鋒工廠實現createBg函數創建黑底,茶苑工廠實現createBg函數創建白底。對于界面來說,不關心如何生成底框,只關心使用的工廠是哪個。還有一個例子就是邊茶的月卡,因為歷史性的原因,邊茶的月卡本就是完全不同的代碼,然后單獨一個平臺的月卡里面還分為貴賓卡,連簽卡,普通卡,然后這些還不是同一個人寫的,web也不是一個人寫的,代碼耦合性非常高,接到修改月卡的需求我都頭痛,其實如果有時間重構的話,使用抽象工廠去寫,定義一些基本函數,比如展示頁面,當天簽到,一鍵簽到,額外簽到等,不同的月卡工廠去生成不同的月卡,代碼就會清晰很多。
設計原則:
6.要依賴抽象,不要依賴具體類。
?
6.命令模式:
定義:將一個請求封裝為一個對象,使發出請求的責任和執行請求的責任分割開。這樣兩者之間通過命令對象進行溝通,這樣方便將命令對象進行儲存、傳遞、調用、增加與管理。
UML圖:
?使用場景:
舉例:比如我在最開始提到的,把動作封裝成一個個函數,使用場景就在轉盤類游戲,燈光從一個節點亮跳到下一個節點,最開始我使用的是一個個回調互相嵌套的方式,后來發現出bug很難查問題,而且非常不利于修改拓展,后來參考了別的代碼,提前把所有跳躍動作封裝到一個表中依次調用,代碼清晰了很多。還有一個功能,就是進入大廳最開始的彈窗邏輯其實就是命令模式的最佳使用者,策劃要求彈窗邏輯是有順序的,有選擇的,有范圍的彈,使用命令模式就很好的滿足這些要求,把要彈窗的請求封裝成一個函數,彈窗邏輯只需要調用函數,函數內部去做ui展示,在實現彈窗邏輯的命令模式后還有一個好處,由于數據請求不是統一返回的,命令模式可以非常方便的在彈窗流程中加入新的彈窗命令。最后就是我最近考慮的一個問題,各個ui之間的彈窗調用和回調,比如說從vip跳到商城,關閉商城之后會再次彈vip,目前的解決方法是在目標ui中加入一個回調,可以由調用者賦值,那么是否也可以用命令模式來解決這個問題呢?
?
7.適配器模式:
定義:的定義如下:將一個類的接口轉換成客戶希望的另外一個接口,使得原本由于接口不兼容而不能一起工作的那些類能一起工作。
UML圖:
?
使用場景:
- 以前開發的系統存在滿足新系統功能需求的類,但其接口同新系統的接口不一致。
- 使用第三方提供的組件,但組件接口定義和自己要求的接口定義不同。
舉例:最直接的例子就是邊茶移動大廳的兩套代碼,由于歷史原因很多相同功能的代碼卻不同,比如刷新金幣這個函數,如果不使用適配器模式在后續開發中,同樣的功能代碼卻還要特意修改一下函數名,所以我加了一個adapt類,可以讓邊茶使用同一套代碼。
設計原則:
7.最少知識原則:只和你的密友談話。
?
8.模板方法模式:
定義:在每一個方法中定義一個算法的骨架,而將一些步驟延遲到子類中。模板方法使得子類可以在不改變算法結構的情況下,重新定義算法中的某些步驟。
UML圖:
使用場景:
舉例:比如移動大廳的玩家中心,就是使用的模版方法模式。公共父類實現了是否展示左側按鈕,按鈕點擊邏輯,刷新紅點,刷新不同模塊,初始化展示邏輯,子類只需要實現父類show函數來實現具體功能ui邏輯。
設計原則:
8.好萊塢原則:別找我,我會找你。
?
9.組合模式:
定義:允許你將對象組成樹結構來表現“整體/部分”的層次結構。組合能讓客戶以一致的方法處理個別對象和對象組合。
UML圖:
?
使用場景:
舉例:其實cocos所使用的可視化ui界面就是用的組合模式,主場景scene,然后下面會有各類其他組件。組合模式最大的好處就是所有實例都是同源的,他們擁有一些公用的方法,也有一些特有的方法,并且可以互相組合,在處理時不進行區分。
?
10.狀態模式:
定義:對有狀態的對象,把復雜的“判斷邏輯”提取到不同的狀態對象中,允許狀態對象在其內部狀態發生改變時改變其行為。
UML圖:
使用場景:
- 當一個對象的行為取決于它的狀態,并且它必須在運行時根據狀態改變它的行為時,就可以考慮使用狀態模式。
- 一個操作中含有龐大的分支結構,并且這些分支決定于對象的狀態時。
舉例:老何在分享游戲邏輯代碼時曾提到過狀態模式的使用,當游戲邏輯處于不同的狀態時,對于同一個事件會有不同的處理方式。回到上面月卡的那個例子,如果產運有一個需求,月卡可以相互切換,但有一個一鍵簽到當前頁面的按鈕一直存在,點擊就可以領取當前月卡的所有獎勵,此時只需在本地維護一個月卡引用,當切換月卡頁面時,保存到月卡引用上,調用一鍵簽到當前頁面函數,這是不是就有一點狀態模式的味道了。
?
推薦書籍:《Head First設計模式》
推薦網站:軟件設計模式概述
如果覺得講的還不錯,請為我點贊~
如果認為認知有沖突歡迎留言討論~
非常感謝~
?
總結
- 上一篇: Reactive 响应式编程简单使用
- 下一篇: C++ | PaddleOCR GPU版