程式碼要寫成別人看不懂的樣子(十)
本篇文章參考書籍《JavaScript設計模式》–張容銘
前言
各位寫程式碼的時候,經常會出現條件判斷吧,那麼條件判斷裡面臉最熟的當屬 if 了吧,這個東西在我們開始編碼的時候,真的是隔一段時間不寫,就渾身難受。
但是呢,if 這個東西,哪怕是再簡單的判斷,也會有隱含問題,而且在程式碼可讀性上,條件判斷的效果很不友好,當存在多重判斷的時候,簡直就是災難。
所以為了解決條件判斷的這些弊端,狀態模式應運而生。
狀態模式
狀態模式:當一個物件的內部狀態發生改變時,會導致其行為的改變,這看起來像是改變了物件。
我們可以將不同的狀態結果封裝在狀態物件內部,然後該狀態物件返回一個可被呼叫的介面方法,用於呼叫狀態對下內部某種方法。做法如下:
//狀態物件
var ResultState = function() {
//結果儲存在內部狀態中
var States = {
//每種狀態作為一種獨立方法儲存
state0:funciton() {
//處理結果0
console.log('這是第一種情況');
},
state1:funciton() {
//處理結果1
console.log('這是第二種情況');
},
state2:funciton() {
//處理結果2
console.log('這是第三種情況');
},
state3:funciton() {
//處理結果3
console.log('這是第四種情況');
},
}
//獲取某一種狀態,並執行其方法
function show(result) {
States['state' + result] && States['state' + result]();
}
return {
//返回撥用狀態方法介面
show: show
}
} ();
當我們想呼叫第三種結果的時候,我們就可以按照下面這種方法實現。
//展示結果3
ResultState.show(3);
上面方法展示了狀態模式的基本雛形,該模式主要目的就是將條件判斷的不同結果轉化為狀態物件的內部狀態。由於是內部狀態,所以建立時設定成私有變數,只提供一個介面給外部進行增刪改查,方便了我們對狀態物件中,內部物件的管理。
大家小時候都玩過超級瑪麗吧,我們操作人物的時候可以跳躍,奔跑,蹲下,丟飛鏢等,這些都是一個一個的狀態,如果我們用 if 或者 switch 條件判斷的話,那麼後期會相當難維護,因為增加或者刪除一個狀態需要修改的地方太多了,這時使用狀態模式幫我們管理就再合適不過了。
對於超級瑪麗,有的時候需要跳起來丟飛鏢,有的時候需要蹲下丟飛鏢,有的時候需要跑起來丟,這些組合狀態如果用 if 或者 switch 判斷的話,無形當中增加的成本是無法想象的。例如:
//單動作條件判斷 每增加一個動作就需要新增一個判斷
var lastAction = '';
function changeMarry(action) {
if(action = 'jump') {
//跳躍動作
} else if(action = 'move') {
//移動動作
} else {
//預設情況
}
lastAction = action;
}
//符合動作對條件判斷的開銷是翻倍的
var lastAction1 = '';
var lastAction2 = '';
function changeMarry(action1, action2) {
if(action1 = 'throw') {
//丟飛鏢
} else if(action1 = 'jump') {
//跳躍
} else if(action1 = 'jump' && action2 = 'throw') {
//跳起來扔飛鏢
} else if(action1 = 'move' && action2 = 'throw') {
//移動中扔飛鏢
}
//保留上一個動作
lastAction1 = action1 || '';
lastAction2 = action2 || '';
}
上面程式碼的可維護性和可讀性都是很差的,日後對於新動作的新增或者修改原有動作的成本都是很大的。那麼為了解決這一問題我們引入狀態模式。解決思路如下:
1.建立一個狀態物件。
2.內部儲存狀態變數。
3.內部封裝好每種動作對應的狀態。
4.狀態物件返回一個介面物件,可以對內部狀態進行修改或者呼叫。
//建立超級瑪麗狀態類
var MarryState = function() {
//內部狀態私有變數
var _currentState = {},
//動作與狀態方法對映
states = {
jump: function() {
//跳躍
console.log('jump');
},
move: function() {
//移動
console.log('move');
},
throw: function() {
//丟飛鏢
console.log('throw');
},
squat: function() {
//蹲下
console.log('squat');
}
};
//動作控制類
var Action = {
//改變狀態方法
changeState: function() {
//組合動作通過傳遞多個引數實現
var arg = arguments;
//重置內部狀態
_currentState = {};
//如果有動作則新增動作
if(arg.length) {
//遍歷動作
for(var i = 0; len = arg.length; i < len; i++) {
//向內部狀態中新增動作
_currentState[arg[i]] = true;
}
}
//返回動作控制類
return this;
},
//執行動作
goes: function() {
console.log('觸發一次動作');
//遍歷內部狀態儲存的動作
for(var i in currentState) {
//如果該動作存在則執行
states[i] && states[i]();
}
return this;
}
}
//返回介面方法change ,goes
return {
change: Action.changeState,
goes: Action.goes
}
}
超級瑪麗的狀態建立完成了,接下來就是使用了。
//建立一個超級瑪麗
var marry = new MarryState();
marry
.change('jump', 'throw') //新增跳躍與丟飛鏢動作
.goes() //執行動作
.goes() //執行動作
.change('throw') //新增丟飛鏢動作
.goes(); //執行動作
輸出結果如下:
//觸發一次動作
//jump
//throw
//觸發一次動作
//jump
//throw
//觸發一次動作
//throw
改變狀態類一個狀態,就改變了狀態物件的執行結果,是不是有點類似物件,讓這些狀態管理一下清晰了起來。
策略模式
各位最近剛過完雙十一吧,參與了將近五千億的專案,感覺怎麼樣?湊滿減的時候是不是感覺自己的數學水平又重新到達巔峰了。雙十一活動有很多哈,比如滿300減40,滿200減25等等,有的商品不參與滿減活動,但是可以先付定金,然後再付尾款,這一點像不像上面的剛介紹的狀態模式,會用到很多條件判斷。
但是,情況又不太一樣,不管商品採用哪種方案,一般都會只選取其中一種,比如該商品滿300減40了,就不參與滿200減25的活動了,如果使用狀態模式的話,那就會為每一種商品,建立一個狀態物件,這樣做就會產生程式碼冗餘。
這個時候就需要用策略模式來解決當前問題了。策略模式結構上與狀態模式很像,都是再內部封裝一個物件,然後返回的介面物件實現對內部物件的呼叫。
不同點是,策略模式不需要管理狀態,狀態間沒有依賴關係,策略之間可以相互替換,在策略物件內部儲存的是相互獨立的一些演算法。
//價格策略物件
var PriceStrategy = function() {
//內部演算法物件
var strategy= {
//滿300減40
reduce40: function(price) {
return +price - ~~(price / 300) * 40; //~~表示parseInt
},
//滿200減25
reduce25: function(price) {
return +price - ~~(price / 200) * 25;
},
//定金50付尾款
deposit: function(price) {
return +price - 50;
},
}
//策略演算法呼叫介面
return function(algorithm, price) {
//如果演算法存在則呼叫演算法,否則返回false
return strategy[algorithm] && strategy[algorithm](price)
}
} ();
策略物件已經寫出來了,我們可以看看怎麼獲取。
var price = PriceStrategy('reduce40', 1000);
console.log(price); // 880
策略模式其實應用的場景很多,比如 JQuery 動畫中的緩衝函式,驗證表單時候的正則演算法。
//表單正則策略物件
var InputStrategy = function() {
var strategy = {
//是否為空
notNull: funetion(value) {
return /\s+/.test(value) ? '請輸入內容' : '';
},
//是否是一個數字
number: funetion(value) {
return /^[0-9]+(\.[0-9]+)?$/.test(value) ? '' : '請輸入數字';
},
}
return {
//驗證介面 type 演算法 value 表單值
check: function(type, value) {
//去除收尾空白符
value = value.replace(/^\s+|\s+$/g, '');
return strategy[type] ? strategy[type](value) : '沒有該型別檢測方法';
},
addStrategy: function(type, fn) {
strategy[type] = fn;
}
}
} ();
上面例子中,我們新增加了一個 addStrategy 用來增加新的策略,這個介面可以幫我們不用修改策略物件內部方法,便可以新增新的策略。用法如下:
//擴充可延續演算法
InputStrategy.addStrategy('nickname', function(value) {
return /^[a-zA-Z]\w{3,7}$/.test(value) ? '' : '請輸入4-8位暱稱, 如:LHXX';
});
本片文章的兩個設計模式還是很常見的,也比較簡單,大家用一兩次就能學會,使用後會給以後需求增加帶來很多方便。
相關文章
- 你以為這樣寫Java程式碼很6,但我看不懂Java
- 高手是如何寫出讓別人看不懂的選擇器
- 在別人寫的程式碼上做修改我是這樣保證正確性
- 怎樣用程式碼寫出99成法口訣!
- 人臉識別的簡要介紹(附例項、Python程式碼)Python
- JavaScript寫程式碼要規範JavaScript
- 大牛的程式碼是這樣寫的
- 怎樣保證我的程式碼不會被別人破壞?
- 怎麼樣零程式碼零成本搭建個人網站?網站
- 何為程式碼質量?——用腦子寫程式碼
- 編寫高質量程式碼的十個祕訣
- 程式碼都寫不完,還寫個錘子註釋!
- 幽默:把Java寫成Python風格的程式碼JavaPython
- 程式設計師懵逼時刻:幾個月後,之前自己寫的程式碼也看不懂了?程式設計師
- 你手中的程式碼,可能變成他們的子彈
- redux樣板程式碼簡化寫法Redux
- 七個不一樣的Python程式碼寫法,讓你寫出一手漂亮的程式碼Python
- 這樣可以寫出無法維護的程式碼
- Obsidian自定義程式碼塊樣式成Typora
- 據說別人的程式碼能殺人於無形???
- 十年程式設計師的告誡:千萬不要重寫程式碼!程式設計師
- 這樣規範寫程式碼,同事直呼“666”
- 對談 MoonBit:AI 時代的程式語言應該是什麼樣子的?丨編碼人聲AI
- 反-反爬蟲:用幾行程式碼寫出和人類一樣的動態爬蟲爬蟲行程
- 測試工程師必須要會寫程式碼嗎?工程師
- 對話MySQL之父Monty:程式碼要寫到100歲MySql
- 如何閱讀別人的C/C++程式碼C++
- SnippetsLab - 像納博科夫寫小說一樣寫程式碼
- PHPer這樣寫程式碼也許更優雅PHP
- 這樣寫程式碼,真是帥到沒有朋友
- 寫給程式設計師的機器學習入門 (十) - 物件識別 Faster-RCNN - 識別人臉位置與是否戴口罩程式設計師機器學習物件ASTCNN
- 萬字長文!雲遊戲這顆出膛十年的“子彈”,成為主流還要飛多久?遊戲
- 別人寫的CSS,你敢用嗎?CSS
- 執行效率高的程式碼-可以這樣寫出來~
- 要炸了!剛寫完這段程式碼,就被開除了
- 十年架構師:我是這樣手寫Spring的,用300行程式碼體現優雅之道架構Spring行程
- Github Copilot機器人寫的程式碼引發糾紛Github機器人
- 你還在手寫TS型別程式碼嗎型別