前言
在軟體工程中,設計模式(design pattern)是對軟體設計中普遍存在(反覆出現)的各種問題,所提出的解決方案。
設計模式並不直接用來完成程式碼的編寫,而是描述在各種不同情況下,要怎麼解決問題的一種方案。
設計模式能使不穩定轉為相對穩定、具體轉為相對抽象,避免會引起麻煩的緊耦合,以增強軟體設計面對並適應變化的能力
——維基百科
設計模式是一種軟體開發的思想,有益於降低程式碼的耦合性,增強程式碼的健壯性。往往在大型專案中用的比較多。
今天就來介紹一種可以替代選擇運算的設計模式——策略模式。
介紹
策略模式作為一種軟體設計模式,指物件有某個行為,但是在不同的場景中,該行為有不同的實現演算法。比如每個人都要“交個人所得稅”,但是“在美國交個人所得稅”和“在中國交個人所得稅”就有不同的算稅方法。
策略模式:
- 定義了一族演算法(業務規則);
- 封裝了每個演算法;
- 這族的演算法可互換代替(interchangeable)。
——維基百科
可以看出,為應對不同場景所導致演算法不同,基於工廠模式將各個演算法進行封裝成類,再進行使用,這就是策略模式。
案例
我們來一個例子,一般情況下,如果我們要做資料合法性驗證,很多時候都是按照 swith 語句來判斷(也可以是 if,elseif,else 的結構),但是這就帶來幾個問題:
- 如果增加需求的話,我們還要再次修改這段程式碼以增加邏輯。
- 在進行單元測試的時候也會越來越複雜。
程式碼如下:
validator = {
validate: function(value, type) {
switch (type) {
case "isNonEmpty ": {
return true;
}
case "isNumber ": {
return true;
break;
}
case "isAlphaNum ": {
return true;
}
default: {
return true;
}
}
}
};
// 測試
alert(validator.validate("123", "isNonEmpty"));
複製程式碼
那如何來避免上述程式碼中的問題呢,根據策略模式,我們可以將相同的工作程式碼單獨封裝成不同的類,然後通過統一的策略處理類來處理,OK,我們先來定義策略處理類,程式碼如下:
var validator = {
// 所有可以的驗證規則處理類存放的地方,後面會單獨定義
types: {},
// 驗證型別所對應的錯誤訊息
messages: [],
// 當然需要使用的驗證型別
config: {},
// 暴露的公開驗證方法
// 傳入的引數是 key => value對
validate: function(data) {
var i, msg, type, checker, result_ok;
// 清空所有的錯誤資訊
this.messages = [];
for (i in data) {
if (data.hasOwnProperty(i)) {
type = this.config[i]; // 根據key查詢是否有存在的驗證規則
checker = this.types[type]; // 獲取驗證規則的驗證類
if (!type) {
continue; // 如果驗證規則不存在,則不處理
}
if (!checker) {
// 如果驗證規則類不存在,丟擲異常
throw {
name: "ValidationError",
message: "No handler to validate type " + type
};
}
result_ok = checker.validate(data[i]); // 使用查到到的單個驗證類進行驗證
if (!result_ok) {
msg = "Invalid value for *" + i + "*, " + checker.instructions;
this.messages.push(msg);
}
}
}
return this.hasErrors();
},
// helper
hasErrors: function() {
return this.messages.length !== 0;
}
};
複製程式碼
然後剩下的工作,就是定義 types 裡存放的各種驗證類了,我們這裡只舉幾個例子:
// 驗證給定的值是否不為空
validator.types.isNonEmpty = {
validate: function(value) {
return value !== "";
},
instructions: "傳入的值不能為空"
};
// 驗證給定的值是否是數字
validator.types.isNumber = {
validate: function(value) {
return !isNaN(value);
},
instructions: "傳入的值只能是合法的數字,例如:1, 3.14 or 2010"
};
// 驗證給定的值是否只是字母或數字
validator.types.isAlphaNum = {
validate: function(value) {
return !/[^a-z0-9]/i.test(value);
},
instructions: "傳入的值只能保護字母和數字,不能包含特殊字元"
};
複製程式碼
使用的時候,我們首先要定義需要驗證的資料集合,然後還需要定義每種資料需要驗證的規則型別,程式碼如下:
var data = {
first_name: "Tom",
last_name: "Xu",
age: "unknown",
username: "TomXu"
};
validator.config = {
first_name: "isNonEmpty",
age: "isNumber",
username: "isAlphaNum"
};
複製程式碼
最後,獲取驗證結果的程式碼就簡單了:
validator.validate(data);
if (validator.hasErrors()) {
console.log(validator.messages.join("\n"));
}
複製程式碼
總結
策略模式定義了一系列演算法,從概念上來說,所有的這些演算法都是做相同的事情,只是實現不同,他可以以相同的方式呼叫所有的方法,減少了各種演算法類與使用演算法類之間的耦合。
從另外一個層面上來說,單獨定義演算法類,也方便了單元測試,因為可以通過自己的演算法進行單獨測試。
實踐中,不僅可以封裝演算法,也可以用來封裝幾乎任何型別的規則,是要在分析過程中需要在不同時間應用不同的業務規則,就可以考慮是要策略模式來處理各種變化。
-EFO-
筆者專門在 github 上建立了一個倉庫,用於記錄平時學習全棧開發中的技巧、難點、易錯點,歡迎大家點選下方連結瀏覽。如果覺得還不錯,就請給個小星星吧!?
2019/04/24