JavaScript 設計模式之策略模式

AJie發表於2019-04-23

前言

在軟體工程中,設計模式(design pattern)是對軟體設計中普遍存在(反覆出現)的各種問題,所提出的解決方案。

設計模式並不直接用來完成程式碼的編寫,而是描述在各種不同情況下,要怎麼解決問題的一種方案。

設計模式能使不穩定轉為相對穩定、具體轉為相對抽象,避免會引起麻煩的緊耦合,以增強軟體設計面對並適應變化的能力

——維基百科

設計模式是一種軟體開發的思想,有益於降低程式碼的耦合性,增強程式碼的健壯性。往往在大型專案中用的比較多。

今天就來介紹一種可以替代選擇運算的設計模式——策略模式。

介紹

策略模式作為一種軟體設計模式,指物件有某個行為,但是在不同的場景中,該行為有不同的實現演算法。比如每個人都要“交個人所得稅”,但是“在美國交個人所得稅”和“在中國交個人所得稅”就有不同的算稅方法。

策略模式:

  • 定義了一族演算法(業務規則);
  • 封裝了每個演算法;
  • 這族的演算法可互換代替(interchangeable)。

——維基百科

JavaScript 設計模式之策略模式

可以看出,為應對不同場景所導致演算法不同,基於工廠模式將各個演算法進行封裝成類,再進行使用,這就是策略模式。

案例

我們來一個例子,一般情況下,如果我們要做資料合法性驗證,很多時候都是按照 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

AJie

相關文章