前言 有關設計模式的學習資料中,大部分都是以 java 語言實現的,畢竟 java 作為老牌面向對象的語言最能說明設計模式的核心概念,所以 js 的相關設計模式的學習資料也大多使用 class 類實現,本文記錄下 js 使用函數實現策略模式和狀態模式設計模式的方式,更有助於理解策略模式和狀態模式如何 ...
前言
有關設計模式的學習資料中,大部分都是以 java 語言實現的,畢竟 java 作為老牌面向對象的語言最能說明設計模式的核心概念,所以 js 的相關設計模式的學習資料也大多使用 class 類實現,本文記錄下 js 使用函數實現策略模式和狀態模式設計模式的方式,更有助於理解策略模式和狀態模式如何在實際工作中運用。
策略模式
定義一系列的演算法,把它們一個一個封裝起來,並且使它們可以相互替換。
目的:將演算法的使用和演算法實現分離開來
優點:
- 利用組合、委托、多態等思想,可以解決多重條件選擇語句問題
- 策略模式提供了對開放—封閉原則的完美支持,將演算法封裝在獨立的strategy中,使得它們易於切換,易於理解,易於擴展
- 策略模式中的演算法也可以復用在系統的其他地方,從而避免許多重覆的複製粘貼工作
- 在策略模式中利用組合和委托來讓 Context 擁有執行演算法的能力,這也是繼承的一種更輕便的替代方案
缺點:
- 代碼會增加許多策略類和策略對象
- 需要全面瞭解各種 stragety, stragety要向客戶暴露它的所有實現,違反最少知識原則
狀態模式
允許一個對象在其內部狀態改變時改變它的行為,對象看起來似乎修改了它的類。
優點:
- 狀態模式定義了狀態與行為之間的關係,並將它們封裝在一個類里。通過增加新的狀態類,很容易增加新的狀態和轉換
- 避免 Context 無限膨脹,狀態切換的邏輯被分佈在狀態類中,也去掉了 Context 中原本過多的條件分支
- 用對象代替字元串來記錄當前狀態,使得狀態的切換更加一目瞭然
- Context 中的請求動作和狀態類中封裝的行為可以非常容易地獨立變化而互不影響
缺點:
- 會在系統中定義許多狀態類,而且系統中會因此而增加不少對象
性能優化點
- 1、僅當 state 對象被需要時才創建並隨後銷毀,用於節省記憶體,但不常變動的
- 2、一開始就創建好所有的狀態對象,並且始終不銷毀它們,用於狀態經常變動的
- 由於邏輯分散在狀態類中,雖然避開了不受歡迎的條件分支語句,但也造成了邏輯分散的問題,我們無法在一個地方就看出整個狀態機的邏輯
相同點
- 都有一個上下文、一些策略或狀態類
- 上下文把請求委托給這些類來執行
區別
狀態模式
- 【不同事情 】狀態模式各狀態之間的切換,做不同的事情;
- 【不能互相替換 】狀態模式各個狀態的同一方法做的是不同的事,不能互相替換,狀態和狀態行為是早已被封封裝好的,狀態之間的切換也早被規定完成,改變模式這個行為發生在狀態內部,使用者不需要瞭解改變的細節;
- 【封裝狀態】狀態模式封裝了對象的狀態;
- 【狀態不可重用】因為狀態是跟對象密切相關的,它不能被重用;
- 【持有context 】在狀態模式中,每個狀態通過持有Context的引用,來實現狀態轉移;。
策略模式
- 【同樣的事情】策略模式更側重於根據具體情況選擇策略,做同樣的事情;
- 【可替換】策略模式各個策略之間是可替換的,平等又平行,互相之間沒有任何聯繫,需熟知各個策略、各類的作用,以便隨時切換演算法;
- 【封裝演算法和策略】策略模式封裝演算法或策略;
- 【策略可重用】策略模式通過從Context中分離出策略或演算法,我們可以重用它們;
- 【不持有context】但是每個策略都不持有Context的引用,它們只是被Context使用。context持有對某個策略對象的引用。
聯繫
狀態模式和策略模式都是為具有多種可能情形設計的模式,把不同的處理情形抽象為一個相同的介面,符合對擴展開放,對修改封閉的原則。(優化多個條件判斷語句的業務邏輯)
實踐
策略模式
var S = function( salary ){
return salary * 4;
};
var A = function( salary ){
return salary * 3;
};
var B = function( salary ){
return salary * 2;
};
// 這裡的 context 是 calculateBonus
var calculateBonus = function( func, salary ){
return func( salary );
};
calculateBonus( S, 10000 ); // 輸出:40000
// 定義各個策略對象,每個策略對象都不持有 context 的引用,僅僅被 context 使用
const add = {
calculate: function (a, b) {
return a + b;
},
};
const subtract = {
calculate: function (a, b) {
return a - b;
},
};
const multiply = {
calculate: function (a, b) {
return a * b;
},
};
// 策略管理器,這裡 Calculator 代表了 context,context 擁有執行不同演算法的能力,傳參為要執行的策略,context 持有對某個策略對象的引用
function Calculator(strategy) {
this.strategy = strategy;
this.setStrategy = function (newStrategy) {
this.strategy = newStrategy;
};
this.calculate = function (a, b) {
return this.strategy.calculate(a, b);
};
}
// 使用
// 傳入或設置不同的策略,執行結果函數得到結果
const calculator = new Calculator(add);
console.log(calculator.calculate(2, 3)); // 5
calculator.setStrategy(subtract);
console.log(calculator.calculate(2, 3)); // -1
calculator.setStrategy(multiply);
console.log(calculator.calculate(2, 3)); // 6
狀態模式
/ 狀態對象
// 每個狀態對象持有對傳入狀態的引用,以便流轉至下一個狀態
const redLight = {
name: '紅燈',
nextState: function () {
return greenLight;
},
};
const yellowLight = {
name: '黃燈',
nextState: function () {
return redLight;
},
};
const greenLight = {
name: '綠燈',
nextState: function () {
return yellowLight;
},
};
// 狀態管理器
function TrafficLightManager(initialState) {
this.state = initialState;
this.changeState = function () {
this.state = this.state.nextState();
};
}
// 使用
// 傳入初始狀態
const trafficLight = new TrafficLightManager(redLight);
console.log(trafficLight.state.name); // 紅燈
// 通過狀態流轉方法切換下一個狀態
trafficLight.changeState();
console.log(trafficLight.state.name); // 綠燈
trafficLight.changeState();
console.log(trafficLight.state.name); // 黃燈
// 封裝執行狀態的請求
var delegate = function (client, delegation) {
return {
buttonWasPressed: function () { // 將客戶的操作委托給 delegation 對象
return delegation.buttonWasPressed.apply(client, arguments);
}
}
};
// 定義狀態機
var FSM = {
off: {
buttonWasPressed: function () {
console.log('關燈');
this.button.innerHTML = '下一次按我是開燈';
this.currState = this.onState;
}
},
on: {
buttonWasPressed: function () {
console.log('開燈');
this.button.innerHTML = '下一次按我是關燈';
this.currState = this.offState;
}
}
};
// 這裡 Light 代表了 context,context 持有各個狀態對象的引用,根據狀態的改變執行不同的行為
var Light = function () {
this.offState = delegate(this, FSM.off);
this.onState = delegate(this, FSM.on);
this.currState = this.offState; // 設置初始狀態為關閉狀態
this.button = null;
};
Light.prototype.init = function () {
var button = document.createElement('button'),
self = this;
button.innerHTML = '已關燈';
this.button = document.body.appendChild(button);
this.button.onclick = function () {
self.currState.buttonWasPressed();
}
};
var light = new Light()
本文大部分總結自書《javascript設計模式與開發實踐》,僅供學習使用,如有錯誤,望大家多多指正。