(十三)其他设计模式
其他設計模式
- 其他設計模式
 - 優先級劃分依據
 - 設計模式有:
 - 創建型
 - 結構型
 - 行為型
 
- 原型模式
 - 對比js中的原型prototype
 
- 橋接模式
 - 設計原則驗證
 
- 組合模式
 - 享元模式
 - 策略模式
 - 模板方法模式
 - 職責鏈模式
 - 命令模式
 - 備忘錄模式
 - 訪問者模式
 - 中介者模式
 - 解釋器模式
 - 關于面試
 - 日常使用
 
其他設計模式
優先級劃分依據
- 不常用
 - 是找不到經典應用場景
 
設計模式有:
創建型
- 原型模式
 
結構型
- 橋接模式
 - 組合模式
 - 享元模式
 
行為型
- 策略模式 、模板方法模式 、職責鏈模式
 - 命令模式 、備忘錄模式 、中介者模式
 - 訪問者模式 、解釋器模式
 
原型模式
- clone自己,生成一個新對象
 - java默認有clone接口,不用自己實現
 
基于一個對象創建一個重復的對象,重新new代價較大的情況下,就拷貝一份。具體實現就是自己實現一個 clone 自己的 API ,外部調用時克隆自己。如 java 內置了clone方法,不用自己定義。
JS 用到不多,因為 JS 中基本不會遇到new代價很大的場景。基本遇到需要創建時,直接重新new就可以了。
不過Object.create用到了原型模式的思想(雖然不是 java 中的 clone ),基于一個原型創建一個對象
// `Object.create` 用到了原型模式的思想(雖然不是 java 中的 clone ) // 基于一個原型創建一個對象 var prototype = {getName: function () {return this.first + ' ' + this.last},say: function () {console.log('hello')} }// 基于原型創建 x var x = Object.create(prototype) x.first = 'A' x.last = 'B' console.log(x.getName()) x.say()// 基于原型創建 y var y = Object.create(prototype) y.first = 'C' y.last = 'D' console.log(y.getName()) y.say()對比js中的原型prototype
- prototype可以理解為ES6 class的一種底層原理
 - 而class是實現面向對象的基礎,而不是服務于某個模式
 - 若干年后ES6全面普及,大家可能會忽略掉prototype
 - 但是Object create卻會長久存在
 
和 js 的 prototype 不一樣。在 ES6 語法中,全部使用 class ,prototype 就成了一種底層的實現機制,而不再對開發人員開放。可能若干年后,ES6 標準統一天下,倒是很多初學者都不知道 prototype 是怎么回事兒。因此,目前 JS prototype 可以看做和 class 是同樣的功能,并不是原型模式。
prototype 將會隱藏,而 Object.create 將會長久保留。
橋接模式
- 用于把抽象化與實現化解耦
 - 使得二者可以獨立變化
 - (未找到JS中的經典應用)
 
橋接(Bridge)是用于把抽象化與實現化解耦,使得二者可以獨立變化。在常見的 JS 代碼中找不到合適的例子,下面例子比較合適。
畫有顏色的圖形,第一種設計方式如下
示例代碼
class ColorShape {yellowCircle() {console.log('yellow circle')}redCircle() {console.log('red circle')}yellowTriangle() {console.log('yellow triangle')}redTriangle() {console.log('red triangle')} }// 測試 let cs = new ColorShape() cs.yellowCircle() cs.redCircle() cs.yellowTriangle() cs.redTriangle()第二種設計方式
示例代碼
class Color {constructor(name) {this.name = name} } class Shape {constructor(name, color) {this.name = namethis.color = color}draw() {console.log(`${this.color.name} ${this.name}`)} }// 測試代碼 let red = new Color('red') let yellow = new Color('yello') let circle = new Shape('circle', red) circle.draw() let triangle = new Shape('triangle', yellow) triangle.draw()顯然,第二種方式更加符合開放封閉原則。
另外一個原則 —— 少繼承,多聚合
設計原則驗證
- 抽象與實現分離,解耦
 - 符合開放封閉原則
 
組合模式
- 生成樹形結構,表示“整體-部分”關系
 - 讓整體和部分都具有一致的操作方式
 
組合模式,將對象組合成樹形結構以表示“部分-整體”的層次結構,組合模式使得用戶對單個對象和組合對象的使用具有一致性。
按照傳統組合模式中數據結構應該是這樣的
例子
- JS經典應用中,未找到這么復雜的數據類型,
 - 虛擬 DOM 中的 vnode 結構是這種形式,但數據類型簡單。
 - (用JS實現一個菜單,不算經典應用,與業務相關)
 
以上結構可被定義為
{tag: 'div',attr: {id: 'div1',className: 'container'},children: [{tag: 'p',attr: {},children: ['123']},{tag: 'p',attr: {},children: ['456']}] }- 整體和單個節點的操作是一致的
 - 整體和單個節點的數據結構也保持一致
 
關鍵的一點,對于整體還是單個節點的操作都是一致的。例如,以上結構無論是拿到div節點還是p節點,數據結構都是一樣的。
設計原則驗證
 將整體和單個節點的操作抽象出來
 符合開放封閉原則
享元模式
- 共享內存(主要考慮內存,而非效率)
 - 相同的數據,共享使用
 - (JS中未找到經典應用場景)
 
享元模式(Flyweight),運行共享技術有效地支持大量細粒度的對象,避免大量擁有相同內容的小類的開銷(主要考慮空間效率,如內存),使大家共享一個類(元類) 。
就是為了避免開銷過大而共享一些數據,未能找到 JS 中特別符合的例子。但是符合享元模式設計思想的例子有:
無限下拉列表,將事件代理到高層節點上,如下代碼。如果都綁定到<a>標簽,對內存開銷太大。
<!-- 無限下拉列表,將事件代理到高層節點上 --> <!-- 如果都綁定到`<a>`標簽,對內存開銷太大 --> <div id="div1"><a href="#">a1</a><a href="#">a2</a><a href="#">a3</a><a href="#">a4</a><!-- 無限下拉列表 --> </div><script>var div1 = document.getElementById('div1')div1.addEventListener('click', function (e) {var target = e.targetif (e.nodeName === 'A') {alert(target.innerHTML)}}) </script>注意,在此前的代理模式中也用到這個 demo ,不過兩者不沖突,體現的是兩種設計思想而已。
設計原則驗證
 將相同的部分抽象出來
 符合開放封閉原則
策略模式
- 不同策略分開處理
 - 避免出現大量if...else或者switch...case
 - (JS中未找到經典應用場景)
 
主要解決多個if...else或者switch...case的問題。
多用于具體的業務代碼中,因為本教程沒有做一個實戰項目,因此先舉個例子說明一下。例如一次購買行為,針對普通用戶、會員和 vip 都有不同的折扣。普通的實現方式如:
class User {constructor(type) {this.type = type}buy() {if (this.type === 'ordinary') {console.log('普通用戶購買')} else if (this.type === 'member') {console.log('會員用戶購買')} else if (this.type === 'vip') {console.log('vip 用戶購買')}} }// 測試代碼 var u1 = new User('ordinary') u1.buy() var u2 = new User('member') u2.buy() var u3 = new User('vip') u3.buy()使用策略模式之后
class OrdinaryUser {buy() {console.log('普通用戶購買')} } class MemberUser {buy() {console.log('會員用戶購買')} } class VipUser {buy() {console.log('vip 用戶購買')} }var u1 = new OrdinaryUser() u1.buy() var u2 = new MemberUser() u2.buy() var u3 = new VipUser() u3.buy()關鍵在于:把 if…else 拆分開,分出不同的策略,每個策略單獨處理,而不是混在一起
設計原則驗證
 不同策略,分開處理,而不是混合在一起
 符合開放封閉原則
模板方法模式
模板方法模式想要表述的其實特別簡單,就是將分散的一些操作集中起來,例如
class Action {handle() {handle1()handle2()handle3()}handle1() {console.log('1')}handle2() {console.log('2')}handle3() {console.log('3')} }職責鏈模式
- 一步操作可能分位多個職責角色來完成
 - 把這些角色都分開,然后用一個鏈串起來
 - 將發起者和處理者、包括多個處理者之間進行了分離
 
例如一個請假審批,需要組長審批、經理審批、最后總監審批。代碼如下:
// 請假審批,需要組長審批、經理審批、最后總監審批 class Action {constructor(name) {this.name = namethis.nextAction = null}setNextAction(action) {this.nextAction = action}handle() {console.log(`${this.name} 審批`)if (this.nextAction != null) {this.nextAction.handle()}} }let a1 = new Action('組長') let a2 = new Action('經理') let a3 = new Action('總監') a1.setNextAction(a2) a2.setNextAction(a3) a1.handle()職責連模式概念上的用意是:請求者發起請求,但是不知道哪個審批者會審批,因此就弄一個鏈來操作,總有一個節點會審批。例如你請假,組長、經理、和總監最終肯定會有一個人來絕對你能否請假成功。
由此我們可以聯想到 JS 中的鏈式操作 。JS 的鏈式操作只是一種技術上的操作手段,職責連模式要結合業務。即,鏈式操作可以實現職責連模式,也可以不用職責連模式,這取決于業務的需要。
- jQuery 的鏈式操作
 - Promise.then 的鏈式操作
 - Stream pipe 鏈式操作
 
設計原則驗證
 發起者與各個處理者進行隔離
 符合開放封閉原則
命令模式
- 執行命令時,發布者和執行者分開
 - 中間加入命令對象,作為中轉站
 
執行一個命令時,將命令的觸發者和執行者分開,不讓觸發者直接操作命令執行者。
代碼演示
class Receiver {exec() {console.log('執行')} } class Command {constructor(receiver) {this.receiver = receiver}cmd() {console.log('觸發命令')this.receiver.exec()} } class Invoker {constructor(command) {this.command = command}invoke() {console.log('開始')this.command.cmd()} }// 士兵 let soldier = new Receiver() // 小號手 let trumpeter = new Command(soldier) // 將軍 let general = new Invoker(trumpeter) general.invoke()JS中的應用
 網頁富文本編輯器操作,瀏覽器封裝了一個命令對象
 document.execCommand(‘bold’)document.execCommand(‘undo’)
 
實際的例子不是很多,可列舉一個。要做一個 web 富文本編輯器,需要 JS 操作文本的樣式,例如要對選中的文本進行加粗,需要執行document.execCommand('bold')。這個 API 用到的就是命令模式,即我作為使用者是需要下達一個bold命令即可,如何執行我不用關心。撤下就執行document.execCommand('undo'),恢復就執行document.execCommand('redo')。
如何做到撤銷和恢復,可參考備忘錄模式。
設計原則驗證
 命令對象與執行對象分開,解耦
 符合開放封閉原則
備忘錄模式
- 隨時記錄一個對象的狀態變化
 - 隨時可以恢復之前的某個狀態(如撤銷功能)
 - (未找到JS中經典應用),除了一些工具(如編輯器)
 
保存一個對象的某個狀態,以便在適當的時候恢復對象。例如撤銷功能,日常頁面用的不多,除非一些工具(如編輯器)
代碼演示一下該模式的應用:
// 狀態備忘 class Memento {constructor(content) {this.content = content}getContent() {return this.content} }// 備忘列表 class CareTaker {constructor() {this.list = []}add(memento) {this.list.push(memento)}get(index) {return this.list[index]} }// 編輯器 class Editor {constructor() {this.content = null}setContent(content) {this.content = content}getContent() {return this.content}saveContentToMemento() {return new Memento(this.content)}getContentFromMemento(memento) {this.content = memento.getContent()} }// 測試代碼 let editor = new Editor() let careTaker = new CareTaker() editor.setContent('111') editor.setContent('222') careTaker.add(editor.saveContentToMemento()) // 存儲備忘錄 editor.setContent('333') careTaker.add(editor.saveContentToMemento()) // 存儲備忘錄 editor.setContent('444')console.log(editor.getContent()) editor.getContentFromMemento(careTaker.get(1)) // 撤銷 console.log(editor.getContent()) editor.getContentFromMemento(careTaker.get(0)) // 撤銷 console.log(editor.getContent())編輯器是一個比較小眾的工具,很少有人開發這個。
設計原則驗證
 狀態對象與使用者分開,解耦
 符合開放封閉原則
訪問者模式
將數據操作和數據結構進行分離,使用頻率不高,就不做具體解釋了。
中介者模式
普通的多對象通訊的場景與中介者模式
代碼演示:
class A {constructor() {this.number = 0}setNumber(num, b) {this.number = numif (b) {b.setNumber(num * 100)}} }class B {constructor() {this.number = 0}setNumber(num, a) {this.number = numif (a) {a.setNumber(num / 100)}} }// 測試代碼 let a = new A() let b = new B() a.setNumber(100, b) console.log(a.number, b.number) // 100 10000 b.setNumber(100, a) console.log(a.number, b.number) // 1 100以上代碼中,a和b產生了耦合關系。使用了之后:
代碼演示:
class Mediator {constructor(a, b) {this.a = athis.b = b}setA() {let number = this.b.numberthis.a.setNumber(number * 100)}setB() {let number = this.a.numberthis.b.setNumber(number / 100)} }class A {constructor() {this.number = 0}setNumber(num, m) {this.number = numif (m) {m.setB()}} }class B {constructor() {this.number = 0}setNumber(num, m) {this.number = numif (m) {m.setA()}} }// 測試代碼 let a = new A() let b = new B() let m = new Mediator(a, b) a.setNumber(100, m) console.log(a.number, b.number) // 100 10000 b.setNumber(100, m) console.log(a.number, b.number) // 1 100這個設計模式應該更加體現到業務代碼中,目前沒有找到特別合適的講解示例。
設計原則驗證
 將各關聯對象通過中介者隔離
 符合開放封閉原則
解釋器模式
- 描述語言語法如何定義,如何解釋和編譯
 - 用于專業場景
 
解釋器模式描述了如何為簡單的語言定義一個文法,如何在該語言中表示一個句子,以及如何解析這些句子。
和語言的解析與編譯有關,不常用。除非你想深入這部分的學習,例如想詳細了解如何實現一個正則表達式。
關于面試
能說出課程重點講解的設計模式即可
日常使用
重點講解的設計模式,要強制自己模仿、掌握
 非常用的設計模式,視業務場景選擇性使用
總結
以上是生活随笔為你收集整理的(十三)其他设计模式的全部內容,希望文章能夠幫你解決所遇到的問題。
                            
                        - 上一篇: (十二)状态模式
 - 下一篇: 迷你世界石耙怎么用 24期迷你世界一