Strategy 策略模式

JavaScript Design Pattern「Strategy 策略模式」筆記。

策略模式將各個方法封裝起來,在執行期間選擇適當的演算法。

範例

假設我們想驗證一些資料,其中想驗證各欄位

我們可以使用傳統的「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

JavaScript Design Pattern - Strategy 策略模式

Name 欄位是要被檢測的,檢測條件為不可為空,而 Mobile 欄位不被檢測。因此當 Name 欄位有值,例如:Apple 或 Hello 時,出現訊息「Everything is OK!」表示通過驗證;而 Name 欄位無值的時候,則出現錯誤訊息「Invalid value for _fieldName_, the value cannot be empty」。

看程式碼

推薦閱讀


這篇文章的原始位置在這裡-JavaScript Design Pattern - Strategy 策略模式

由於部落格搬遷至此,因此在這裡放了一份,以便閱讀;部份文章片段也做了些許修改,以期提供更好的內容。

Design Pattern NaN javascript 設計模式