javascript設計模式-策略模式

在我最近的一個項目中,有一個模塊是關於表單校驗的需求,需求大致是驗證輸入不能空不能夠重複的字符串等等,檢驗的規則大概有10種,而我的同事爲了檢驗這樣的表單功能,則編寫大量的if-else代碼,僞代碼如下:

if (value.length ===0) {
      // todo
    } else if (value.length < 6) {
      //todo
    } else if (.....) {
      //todo
    }

可以發現上面的代碼十分簡單,也非常實用,但是也存在明顯的缺點:

  1. 包含了大量的if-else語句,這些語句覆蓋了大量的邏輯。
  2. 上述代碼缺乏彈性,如果增加了新的需求,則必須重複的添加else if。
  3. 邏輯的複用性很差,我如果是其它的模塊也操作相似的邏輯,則必須全部都複製一遍過去,造成一種很臃腫和很噁心的代碼。
    爲了解決以上存在的問題,我們就很自然聯想到設計模式中的一種-策略模式:
    策略模式的定義:

定義一系列的算法,把他們一個個封裝起來,並且使它們可以相互替換。

又比如我們現在有如下的場景:

如果績效爲S的人,那麼他的年終獎則是他工資的4倍,如果績效爲A的人,那麼他的年終獎則是他工資的3倍,如果績效爲B的人,那麼他的年終獎則是他工資的2倍。

那麼我們就自然的想到編寫以下的代碼:

	let calculateBonus = (level,salary)=>{
      if (level === "S") {
        return salary*4
      }else if (level === "A") {
        return salary*3
      }else if (level === "B"){
        return salary * 2
      }
    }
    console.log(calculateBonus("A",200)) // 800
    console.log(calculateBonus("B",200)) // 600

那麼其實這種代碼,在某種程度上來,看着是很蠢的,也不符合,現在前端高大上,逼格高的作風。所以我們需要把它給改造一下:

let strategies = {
      "S": function (salary) {
        return salary * 4
      },
      "A": function (salary) {
        return salary * 3
      },
      "B": function (salary) {
        return salary * 2
      }
    }
    let calcteBonus = (level, salary) => {
      return strategies[level](salary);
    }
    console.log(calcteBonus("S", 200)) // 800
    console.log(calculateBonus("B", 200)) // 600

上面的代碼,我們通過構造strategies 對象,來存放各個邏輯的函數。實際上在javascript語言中,函數也是對象,函數也可以作爲value來輸出。
通過以上的改造,我們可以看到代碼的結構更加簡潔了,else-if的語句也不存在了,瞬間感覺B格就高了很多。當然了,也不是說else-if不好,只是我們有更好實現這種需求的方式,應當選擇更爲簡潔和聰明的方式,也方便日後的維護。

改造表單驗證的代碼:
我們回到一開始的表單需求,我們放棄大量elseif的方式,從而採用更爲簡潔和聰明的形式。假設我們現在有以下的需求爲:

  1. 用戶名不能爲空
  2. 密碼長度不能少於6位
  3. 手機號碼必須符合格式
<body>
  <form>
    <p>
      用戶名:<input type="text" id="username">
    </p>
    <p>
      密碼:<input type="text" id="password">
    </p>
    <p>
      電話:<input type="text" id="telphone">
    </p>
    <p>
      <button id="btn" type="button">按鈕</button>
    </p>
  </form>
  <script>
    var oBtn = document.getElementById("btn");
    var oUserName = document.getElementById("username");
    var oPassword = document.getElementById("password");
    var oTelphone = document.getElementById("telphone");
    oBtn.onclick = function () {
      if (oUserName.value === "") {
        alert("輸入不能爲空");
        return false;
      }
      if (oPassword.value.length < 6) {
        alert("密碼不能少於6位");
        return false;
      }
      if (!/(^1[3|5|8[0-9]{9}]$)/.test(oTelphone.value)) {
        alert("手機號碼格式不正確!");
        return false;
      }
    }
  </script>
</body>

我們又自然就編寫以上的代碼,這樣我們又無法避免寫出了大量if-else的代碼。所以,我們又需要把代碼進行重構!

<body>
  <form>
    <p>
      用戶名:<input type="text" id="username">
    </p>
    <p>
      密碼:<input type="text" id="password">
    </p>
    <p>
      電話:<input type="text" id="telphone">
    </p>
    <p>
      <button id="btn" type="button">按鈕</button>
    </p>
  </form>
  <script>
    let oBtn = document.getElementById("btn");
    let oUserName = document.getElementById("username");
    let oPassword = document.getElementById("password");
    let oTelphone = document.getElementById("telphone");
    //校驗規則的對象
    let strategies = {
      isNoEmpty: function (value, msg) {
        if (value.length === 0) {
          return msg;
        }
      },
      minLength: function (value, length, msg) {
        if (value.length < length) {
          return msg;
        }
      },
      isMobile: function (value, msg) {
        if (!/(^1[3|5|8[0-9]{9}]$)/.test(value)) {
          return msg;
        }
      }
    }
    // 構造檢驗類
    let validataFun = function () {
      var validator = new Validator();
      // 添加檢驗規則
      validator.add(oUserName, "isNoEmpty", "用戶名不能爲空!");
      validator.add(oPassword, "minLength:6", "密碼長度不能小於6!");
      validator.add(oTelphone, "isMobile", "手機號碼格式不正確!");
      let msg = validator.start(); // 調用開始檢驗的方法
      return msg;
    }
    function Validator() {
      this.cache = []; // 保存檢驗規則的數組
    }
    // 通過原型掛載add方法
    Validator.prototype.add = function (dom, rule, msg) {
      let ary = rule.split(":");
      this.cache.push(function () { // 把檢驗的函數push進數組
        let strategy = ary.shift(); // 用戶所操作的strategy,刪除第一項的數據,並返回第一項的數據
        ary.unshift(dom.value); // unshift向開頭添加元素,會影響到原數組
        ary.push(msg);
        return strategies[strategy](...ary);
        // return strategies[strategy].apply(dom, ary);
      })
    }
    // 執行檢驗規則
    Validator.prototype.start = function () {
      // console.log(this.cache[0]());
      for (let i = 0; i < this.cache.length; i++) {
        let msg = this.cache[i]();
        if (msg) {
          return msg;
        }
      }
    }

    oBtn.onclick = function () {
      let msg = validataFun();
      if (msg) {
        alert(msg); // 輸出失敗檢驗的信息
        return false;
      }
    }
  </script>
</body>

我們通過策略模式重構代碼後,我們發現可以通過配置方式來改變,某一個input的檢驗規則,例如,我們把用戶名輸入不能少於10個字符,則可以把代碼修改爲:

validator.add(oUserName, "minLength:10", "用戶名長度不能小於10!");

那麼問題來了,我希望用戶名不能爲空的同時,還希望它的長度不能少於6位,那麼上面的代碼則又需要進行修改。我們期望的是按照以下的檢驗規則來進行校驗:

validator.add(oUserName, [{
        strategy: "isNoEmpty",
        msg: "用戶名不能爲空!"
      }, {
        strategy: "minLength:6",
        msg: "用戶名不能少於6位!"
      }])
      validator.add(oPassword, "minLength:6", "密碼長度不能小於6!");
      validator.add(oTelphone, "isMobile", "手機號碼格式不正確!");

這個時候add方法,第二個參數接受的是數組,那麼我們就通過遍歷這個數組,把檢驗規則都push進cache數組中。

<body>
  <form>
    <p>
      用戶名:<input type="text" id="username">
    </p>
    <p>
      密碼:<input type="text" id="password">
    </p>
    <p>
      電話:<input type="text" id="telphone">
    </p>
    <p>
      <button id="btn" type="button">按鈕</button>
    </p>
  </form>
  <script>
    let oBtn = document.getElementById("btn");
    let oUserName = document.getElementById("username");
    let oPassword = document.getElementById("password");
    let oTelphone = document.getElementById("telphone");
    //校驗規則的對象
    let strategies = {
      isNoEmpty: function (value, msg) {
        if (value.length === 0) {
          return msg;
        }
      },
      minLength: function (value, length, msg) {
        if (value.length < length) {
          return msg;
        }
      },
      isMobile: function (value, msg) {
        if (!/(^1[3|5|8[0-9]{9}]$)/.test(value)) {
          return msg;
        }
      }
    }
    // 構造檢驗類
    let validataFun = function () {
      var validator = new Validator();
      // 添加檢驗規則
      // validator.add(oUserName, "isNoEmpty", "用戶名不能爲空!");
      // validator.add(oPassword, "minLength:6", "密碼長度不能小於6!");
      // validator.add(oTelphone, "isMobile", "手機號碼格式不正確!");
      validator.add(oUserName, [{
        strategy: "isNoEmpty",
        msg: "用戶名不能爲空!"
      }, {
        strategy: "minLength:6",
        msg: "用戶名不能少於6位!"
      }])
      validator.add(oPassword, [{
        strategy: "minLength:6",
        msg: "密碼不能少於6位"
      }]);
      validator.add(oTelphone, [{
        strategy: "isMobile",
        msg: "校驗規則不正確!"
      }]);
      let msg = validator.start(); // 調用開始檢驗的方法
      return msg;
    }

    function Validator() {
      this.cache = []; // 保存檢驗規則的數組
    }
    // 通過原型掛載add方法
    Validator.prototype.add = function (dom, rule, msg) {
      for (let i = 0; i < rule.length; i++) {
        let ary = rule[i].strategy.split(":");
        this.cache.push(function () { // 把檢驗的函數push進數組
          let strategy = ary.shift(); // 用戶所操作的strategy,刪除第一項的數據,並返回第一項的數據
          ary.unshift(dom.value); // unshift向開頭添加元素,會影響到原數組
          ary.push(rule[i].msg);
          return strategies[strategy](...ary);
          // return strategies[strategy].apply(dom, ary);
        })
      }

    }
    // 執行檢驗規則
    Validator.prototype.start = function () {
      for (let i = 0; i < this.cache.length; i++) {
        let msg = this.cache[i]();
        console.log(msg);
        if (msg) {
          return msg;
        }
      }
    }

    oBtn.onclick = function () {
      let msg = validataFun();
      if (msg) {
        alert(msg); // 輸出失敗檢驗的信息
        return false;
      }
    }
  </script>
</body>

通過策略模式,我們是可以很輕鬆去解決我們日常工作一些比較繁瑣的邏輯,當你的代碼,產生了大量的if-else語法的時候,可以嘗試使用策略模式,那麼你的代碼就變得簡潔和有B格。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章