不少時候會發現本身在寫代碼的時候寫了一坨if else 語句使得本身的代碼看起來很醜,隨着業務量的增大,代碼變得很難維護,以前想到能替換if else的只有switch,其實效果並無明顯的提高,如今在看設計模式方面的知識,發現兩種設計模式可以解決分支判斷的臃腫問題。算法
你們都知道超級瑪麗的遊戲吧,瑪麗要吃蘑菇,他就要挑起,頂出牆壁裏的蘑菇;瑪麗想到懸崖的另外一邊,他就要跳起;瑪麗想躲避被前面的烏龜咬到,他就要開槍打死烏龜;前面飛過炮彈,瑪麗就要蹲下躲避;時間不夠了,就要加速奔跑···設計模式
若是這個遊戲要用if或者switch條件判斷,顯得有些疲憊,若是使用狀態模式將‘跳躍’、‘開槍’、‘蹲下’和‘奔跑’做爲一個個的狀態來開發,以後在遇到不一樣狀況的時候直接使用狀態,思路將變得清晰。this
首先建立一個狀態對象,內部保存狀態變量,而後內部封裝好每種動做對應的狀態,最後狀態對象返回一個接口對象,它能夠對內部的狀態修改或者調用。spa
代碼以下:設計
/* * 超級瑪麗裏面的狀態: 跳躍、開槍、蹲下、奔跑等 */ const MarryState = function() { // 內部狀態私有變量 let _currentState = {}; // 動做與狀態方法映射 const states = { jump() { // 跳躍 console.log('jump'); }, move() { // 移動 console.log('move'); }, shoot() { // 射擊 console.log('shoot'); }, squat() { // 蹲下 console.log('squat'); } }; // 動做控制類 const Action = { // 改變狀態方法 changeState() { // 組合動做經過傳遞多個參數實現 let arg = arguments; // 重置內部狀態 _currentState = {}; if(arg.length) { // 遍歷動做 for(let i = 0,len = arg.length; i < len; i++) { // 向內部狀態中添加動做 _currentState[arg[i]] = true; } } // 返回動做控制類 return this; }, // 執行動做 goes() { console.log('觸發一次動做'); // 遍歷內部狀態保存的動做 for(let i in _currentState) { // 若是改動做存在則執行 states[i] && states[i](); } return this; } }; // 返回接口方法 change、goes return { change: Action.changeState, goes: Action.goes } };
使用方式:code
// 建立一個超級瑪麗 const marry = new MarryState(); marry .change('jump','shoot') // 添加跳躍與射擊動做 .goes() // 執行動做 .goes() // 執行動做 .change('shoot') // 添加射擊動做 .goes(); // 執行動做
能夠發現,狀態模式中的狀態能夠連續使用。對象
原理:將條件判斷的不一樣結果轉化爲對象的內部狀態,做爲對象內部的私有變量。blog
好處:當咱們須要增長、修改、調用、刪除某種狀態方法時就會很容易,也方便了咱們對狀態對象中內部狀態的管理。接口
用途:狀態模式是爲了解決程序中臃腫的分支判斷語句問題,將每一個分支轉化爲一種狀態獨立出來,方便每種狀態的管理又不至於每次執行時遍歷全部分支。遊戲
總之,狀態模式的最終目的是簡化分支判斷流程。
商品的促銷活動。在聖誕節,一部分商品5折出售,一部分商品8折出售,一部分商品9折出售,等到元旦,普通用戶滿100減30,高級VIP用戶滿100減50···
若是這種狀況用if或switch來寫將是一件很費時費力的事情。
並且對於聖誕節或者元宵節,當天的一種商品只有一種促銷策略,根本不用關心其餘的促銷狀態。
首先要將這些促銷算法封裝在一個策略對象內,而後對每種商品的策略調用時,直接對策略對象中的算法調用便可,爲方便咱們的管理與使用,咱們須要返回一個調用接口對象來實現對策略算法對調用。
代碼以下:
/* * 價格策略對象 */ const PriceStrategy = function () { // 內部算法對象 const strategy = { // 100 返 30 return30(price) { // parseInt可經過~~、|等運算符替換,要注意此時price要在[-2147483648,2147483647]之間 // +price 轉換爲數字類型 return +price + parseInt(price / 100) * 30; }, // 100 返 50 return50(price) { return +price + parseInt(price / 100) * 50; }, // 9 折 percent90(price) { // JavaScript 在處理小數乘除法有bug,故運算前轉化爲整數 return price * 100 * 90 / 10000; }, // 8 折 percent80(price) { // JavaScript 在處理小數乘除法有bug,故運算前轉化爲整數 return price * 100 * 80 / 10000; }, // 5 折 percent50(price) { // JavaScript 在處理小數乘除法有bug,故運算前轉化爲整數 return price * 100 * 50 / 10000; } }; // 策略算法調用接口 return function (algorithm, price) { // 若是算法存在,則調用算法,不然返回false return stragtegy[algorithm] && stragtegy[algorithm](price); } }();
使用方式:
const price = PriceStrategy('return50', '343.20');
console.log(price);
代碼以下:
/* * 表單正則驗證側羅對象 */ const InputStrategy = function () { const strategy = { // 是否爲空 notNull(value) { return /\s+/.test(value) }, // 是不是一個數字 number(value) { return /^[0-9]+(\.[0-9]+)?$/.test(value); }, // 是否爲本地電話 phone(value) { // 例:010-94837837 或 0310-8899766 return /^\d{3}\-\d{8}$|^\d{4}\-\d{7}$/.test(value); } }; return { // 驗證接口 type 算法 value 表單值 check(type, value) { // 去除首尾空白符 value = value.replace(/^\s+|\s+$/g, ''); return strategy[type] ? strategy[type](value) : '沒有該類型等檢測方法'; }, // 添加策略 addStrategy(type, fn) { strategy[type] = fn; } } };
能夠發現,表單驗證返回的接口中添加了一個添加策略接口。由於已有的策略即便再多,有時候也不能知足其餘工程師的需求,這樣就能夠加強策略對象的拓展性。
// 拓展 能夠延伸算法 InputStrategy.addStrategy('nickname', function (value) { return /^[a-zA-Z]\w{3,7}$/.test(value); });
策略模式:將定義的一組算法封裝起來,使其相互之間能夠替換。
策略模式不須要管理狀態、狀態間沒有依賴關係、策略之間能夠相互替換、在策略對象內部保存的是相互獨立的一些算法。
策略模式使得算法脫離與模塊邏輯而獨立管理,使咱們能夠專心研發算法,而沒必要受模塊邏輯所約束。
設計模式並非很高深不可理解的一門學問,只是根據經驗把複雜的業務邏輯整理清楚,使之更容易操做化。
根據不一樣的業務邏輯選擇不一樣的設計模式有助於簡化代碼,也有助於代碼的解耦,使得代碼更加有效和可維護。
參考書籍:《JavaScript設計模式》