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 策略模式

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