Strategy 策略模式
15 Jul 2015JavaScript Design Pattern「Strategy 策略模式」筆記。
策略模式將各個方法封裝起來,在執行期間選擇適當的演算法。
範例
假設我們想驗證一些資料,其中想驗證各欄位
- 是否為空(isNonEmpty)
- 是否只填數字(isNumber)
- 是否只填英數(isAlphaNum)
- 是否為 Email(isEmail)
- 字串大小是否在最小範圍內,設定為 3 個 characters 以上(minSize)
- 字串大小是否在最大範圍內,設定為 10 個 characters 以內(maxSize)
我們可以使用傳統的「Switch-Case」或「If-Else」寫法(如下),但若需求變更則必須回來維護這段程式碼(參考自深入理解 JavaScript 系列(33):設計模式之策略模式- 湯姆大叔- 博客園)。
// 一般的寫法
validator = {
validate: function(value, type) {
switch (type) {
case 'isNonEmpty': {
return true;
}
case 'isNumber': {
return true;
}
case 'isAlphaNum': {
return true;
}
default: {
return true;
}
}
},
};
// 測試
var result = validator.validate('123', 'isNonEmpty');
console.log(result); // ture
比較好的寫法是使用「Strategy 策略模式」,單獨定義演算方式,不僅容易維護,也方便測試,
說明
改寫上面的資料驗證程式碼。
var validator = {
types: {}, // 所有的驗證規則皆會存放於此,稍後會個別定義
messages: [], // 個別驗證類型的錯誤訊息
config: {}, // 使用者資料各欄位需要被驗證的類型
validate: function(data) {
// 使用者資料 - 欄位:值
var i, msg, type, checker, result_ok;
this.messages = []; // 清空所有的錯誤信息
for (i in data) {
if (data.hasOwnProperty(i)) {
// 判斷使用者欄位是否需要被驗證
type = this.config[i]; // 如果需要被驗證,則取出相對應的驗證規則
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;
},
};
// 個別定義驗證規則
// 欄位值不可為空
validator.types.isNonEmpty = {
validate: function(value) {
return value !== '';
},
instructions: 'the value cannot be empty',
};
//欄位值只能為數字
validator.types.isNumber = {
validate: function(value) {
return !isNaN(value);
},
instructions: 'the value can only be a valid number, e.g. 1, 3.14 or 2010',
};
// 欄位值是否為英數組合
validator.types.isAlphaNum = {
validate: function(value) {
return !/[^a-z0-9]/i.test(value);
},
instructions: 'the value can only contain characters and numbers, no special symbols',
};
// 欄位值是否為 Email
validator.types.isEmail = {
validate: function(value) {
var re = /^([\w-]+(?:\.[\w-]+)*)@((?:[\w-]+\.)*\w[\w-]{0,66})\.([a-z]{2,6}(?:\.[a-z]{2})?)$/i;
return re.test(value);
},
instructions: 'use valid email format, e.g. @',
};
// 欄位值有最小長度限制,字串大小是否在最小範圍內,設定為 3 個 characters以上 (minSize)
validator.types.minSize = {
validate: function(value) {
return value.length >= 3;
},
instructions: 'min size is 3 characters',
};
// 欄位值有最大長度限制,字串大小是否在最大範圍內,設定為 10 個 characters以內 (maxSize)
validator.types.maxSize = {
validate: function(value) {
return value.length <= 10;
},
instructions: 'max size is 10 characters',
};
// 測試資料
var data = {
first_name: 'Super',
last_name: 'Man',
age: 'unknown',
username: 'o_O',
email: 'example@gmail.com',
confirm_email: 'invalid_email_sample',
password: '12',
};
// 定義測試資料每個欄位需要被驗證的類型
validator.config = {
first_name: 'isNonEmpty',
last_name: 'maxSize',
age: 'isNumber',
username: 'isAlphaNum',
email: 'isEmail',
confirm_email: 'isEmail',
password: 'minSize',
};
// 若有錯誤,則console出錯誤訊息
validator.validate(data);
if (validator.hasErrors()) {
console.log(validator.messages.join('\n'));
}
看程式碼。
Demo
console 視窗顯示訊息如下:
Invalid value for *age*, the value can only be a valid number, e.g. 1, 3.14 or 2010
Invalid value for *username*, the value can only contain characters and numbers, no special symbols
Invalid value for *confirm_email*, use valid email format, e.g. @
Invalid value for *password*, min size is 3 characters
簡單改寫為 Plugin
Name 欄位是要被檢測的,檢測條件為不可為空,而 Mobile 欄位不被檢測。因此當 Name 欄位有值,例如:Apple 或 Hello 時,出現訊息「Everything is OK!」表示通過驗證;而 Name 欄位無值的時候,則出現錯誤訊息「Invalid value for _fieldName_, the value cannot be empty
」。
看程式碼。
推薦閱讀
這篇文章的原始位置在這裡-JavaScript Design Pattern - Strategy 策略模式
由於部落格搬遷至此,因此在這裡放了一份,以便閱讀;部份文章片段也做了些許修改,以期提供更好的內容。