javascript 設計模式之策略模式

文章系列

javascript 設計模式之單例模式javascript

javascript 設計模式之適配器模式html

javascript 設計模式之裝飾者模式java

javascript設計模式之代理模式git

javascript 適配、代理、裝飾者模式的比較github

javascript 設計模式之狀態模式web

javascript 設計模式之迭代器模式算法

javascript 設計模式之策略模式設計模式

javascript 設計模式之觀察者模式markdown

javascript 設計模式之發佈訂閱者模式app

概念

策略模式:定義一系列的算法(這些算法目標一致),把它們一個個封裝起來,而且使它們能夠相互替換。

本文代碼

從公司績效談起

每家公司年終獎的發放都會根據該年度員工表現給予必定的獎懲,固然 A 公司也不例外, A 公司的年終績效制度以下:

  1. 等級爲 S 的,年終獎爲工資的 4倍
  2. 等級爲 A 的,年終獎爲工資的 3 倍
  3. 等級爲 B 的,年終獎爲工資的 2 倍
  4. 等級爲 C 的,年終獎爲工資的 0.3 倍

代碼實現以下:

var calculateBonus = function (performanceLevel, salary) {
    if (performanceLevel == 'S') {
        return salary * 4
    }
    if (performanceLevel == 'A') {
        return salary * 3
    }
    if (performanceLevel == 'B') {
        return salary * 2
    }
    if (performanceLevel == 'C') {
        return salary * 0.3
    }
}
console.info(calculateBonus('S', 20000)) // 80000
console.info(calculateBonus('C', 15000)) // 4500
複製代碼

功能上能夠正常使用,但存在以下幾個缺點:

  1. 違反單一功能原則,在一個函數裏處理了四個績效邏輯,絕對的胖邏輯,這種的鐵定要拆
  2. 違反開放封閉原則,若是要再加個績效爲 D 的邏輯,還得往 calculateBonus 函數裏添加一段邏輯,這樣提測的時候,還要求測試人員重頭將全部績效等級測遍。
  3. 算法複用性差,若是其餘地方須要用到這種計算規則,只能從新輸出(複製、粘貼)

優化一(組合函數)

讓咱們先來解決第三點算法複用性差問題。 把各類算法(即年終獎的計算規則)封裝到一個個獨立的小函數中。 代碼改爲以下:

var performanceS = function (salary) {
    return salary * 4
}
var performanceA = function (salary) {
    return salary * 3
}
var performanceB = function (salary) {
    return salary * 2
}
var performanceC = function (salary) {
    return salary * 0.3
}
var calculateBonus = function (performanceLevel, salary) {
    if (performanceLevel == 'S') {
        return performanceS(salary)
    }
    if (performanceLevel == 'A') {
        return performanceA(salary)
    }
    if (performanceLevel == 'B') {
        return performanceB(salary)
    }
    if (performanceLevel == 'C') {
        return performanceC(salary)
    }
}
console.info(calculateBonus('S', 20000)) // 80000
console.info(calculateBonus('C', 15000)) // 4500
複製代碼

採用組合函數,將每一個績效算法抽離成單獨的函數,是解決了複用問題,若是有別的地方要計算 S 等級的薪資,直接調用 performanceS 函數便可。 但上面一、2兩個問題仍然存在,咱們繼續優化,引出主角策略模式

優化二(策略模式)

設計模式中很重要的一點就是將不變的部分和變化的部分分離出來,而策略模式的目的就是將算法的使用和算法的實現分離開來。

在這個例子中,算法的使用方式是不變的,都是根據等級和薪資計算年終獎;算法的實現是變化的,好比 S 有 S 等級的計算方式, A 有 A 等級的計算方式。

策略模式的組成:

  1. 一組策略類,策略類封裝了具體的算法,並負責具體的計算過程。
  2. 環境類Context,Context接收客戶的請求,隨後把請求委託給某一個策略類。

定義策略類:

//策略類(S)
class performanceS {
    calculate(salary) {
        return salary * 4;
    }
}

//策略類(A)
class performanceA {
    calculate(salary) {
        return salary * 3;
    }
}

//策略類(B)
class performanceB {
    calculate(salary) {
        return salary * 2;
    }
}

//策略類(C)
class performanceC {
    calculate(salary) {
        return salary * 0.3;
    }
}
複製代碼

定義環境類:

// 環境類
class Bonus {
    constructor() {
        this.salary = null;     //原始工資
        this.strategy = null;  //績效公司對應的策略對象
    }
    setSalary(salary) {
        this.salary = salary;  //設置原始工資
    }
    setStrategy(strategy) {
        this.strategy = strategy; //設置員工績效等級對應的策略對象
    }
    getBonus() {//取得獎金數額
        //維持對策略對象的引用
        //委託給對應的策略對象
        return this.strategy.calculate( this.salary );  
    }
}
複製代碼

驗證:

const bonus = new Bonus()
bonus.setSalary( 20000 );
bonus.setStrategy( new performanceS() ); //設置策略對象

console.info(bonus.getBonus()) // 80000
複製代碼

上述展現了策略模式的應用,代碼比較清晰,解決了上述的幾大問題,要再增長個績效 D 的邏輯,不會動到 Bonus 類,只要再定義個 performanceD 策略類便可,在 Bonus 類裏作的事情也很單一,負責接收策略類實例,並調用。

上述展現的策略模式是基於傳統的面嚮對象語言,能夠進一步對這段代碼進行優化,變成JavaScript版本的策略模式。

優化三(JavaScript版本策略模式)

在 JS 中,函數也是對象,因此能夠將策略類直接定義爲函數,並以對象映射形式展現。

//策略對象
var strategies = {
  //一系列算法
  "S": function ( salary ) {
    return salary * 4;
  },
  "A": function ( salary ) {
    return salary * 3;
  },
  "B": function ( salary ) {
    return salary * 2;
  },
  "C": function (salary) {
    return salary * 0.3;
  },
};
複製代碼

同時,也能夠將環境類定義爲函數,改爲以下:

var calculateBonus = function ( level, salary) {
  return strategies[ level ]( salary );    
};
複製代碼

驗證下:

console.log( calculateBonus('S', 20000)); // 80000
複製代碼

再來看下策略模式的定義

定義一系列的算法(這些算法目標一致),把它們一個個封裝起來,而且使它們能夠相互替換。

  • 算法:績效的計算方法
  • 封裝:計算方法被封裝在策略對象內部,達到可複用
  • 相互替換:要更改某個績效時,只要改變參數,不影響函數的調用

更廣義的"算法"

策略模式指的是定義一系列的算法,而且把它們封裝起來,可是策略模式不只僅只封裝算法,咱們還能夠用來封裝一系列的業務規則,只要這些業務規則目標一致,咱們就可使用策略模式來封裝它們;好比表單驗證

表單驗證

需求:

  1. 用戶名不能爲空
  2. 密碼長度不能少於6位
  3. 手機號碼必須符合格式

簡單實現

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>表單驗證</title>
</head>
<body>
  <form action='xxx.com' id='registerForm' method='post'>
    請輸入用戶名:<input type='text' name='userName'/ >
    請輸入密碼:<input type='text' name='password'/ >
    請輸入手機號碼:<input type='text' name='phoneNumber'/ >
    <button>提交</button>
</form>
<script>
    var registerForm = document.getElementById('registerForm');
    
    registerForm.onsubmit = function () {
        if ( registerForm.userName.value === '') {
            alert('用戶名不能爲空');
            return false;
        }
        if (registerForm.password.value.length < 6) {
            alert('密碼長度不能小於6位');
            return false;
        }
        if (!(/^1[3|5|8][0-9]{9}$/.test(registerForm.phoneNumber.value))) {
          alert('手機號碼格式不正確');
          return false;
        }
    }
</script>

</body>
</html>
複製代碼

跟上述績效獎金,存在同樣的問題,函數過於龐大、缺少彈性以及複用性差,下面採用策略模式優化

優化一(策略模式)

採用策略模式,首先要定義策略類,那策略類要先找到算法具體是指什麼:表單驗證邏輯的業務規則

因此定義策略類以下:

// 定義策略類
const strategies = {
    isNonEmpty: function ( value, errorMsg) {
        if ( value === '') {
            return errorMsg;
        }
    },
    minLength: function ( value, length, errorMsg ) {
        if ( value.length < length ) {
            return errorMsg
        }
    },
    isMobile: function ( value, errorMsg) {
        if ( !/(^1[3|5|8][0-9]{9}$)/.test( value )) {
            return errorMsg;
        }
    }
}
複製代碼

接着要定義環境類,在定義環境類以前,先看下客戶通常是怎麼使用的?

var validataFunc = function () {
    //建立一個validator對象
    var validator = new Validator();
    //添加校驗規則
    validator.add( registerForm.userName, 'isNonEmpty', '用戶名不能爲空');
    validator.add( registerForm.password, 'minLength:6', '密碼長度不能少於6位');
    validator.add( registerForm.phoneNumber, 'isMobile', '手機號碼格式不正確');
    var errorMsg = validator.start();
    //返回校驗結果
    return errorMsg;
}
var registerForm = document.getElementById('registerForm');
registerForm.onsubmit = function () {
    var errorMsg = validataFunc();   //若是存在,則說明未經過校驗
    if ( errorMsg ) {
        alert( errorMsg );
        return false; //阻止表單提交
    }
}
複製代碼

從上述代碼中,能夠明確在環境類 Validator 中要有 add 方法,經過 add 方法來添加校驗規則。

同時有 start 方法,經過 start 方法開始校驗,若是有錯誤,那麼就返回錯誤信息( errorMsg )

有了策略對象以及策略對象與環境類( Validator )的橋樑,咱們即可以寫出 Validator 類代碼

class Validator {
    constructor() {
        this.cache = [];  //保存校驗規則 
    }
    //添加檢驗規則函數
    add(dom,rule,errorMsg){
        //把strategy和參數分開'minLength:6' 如'minLength:6' -> ["minLength", "6"]
        let ary = rule.split(':'); 
        this.cache.push ( function () {
            //用戶挑選的strategy ["minLength", "6"] -> 'minLength'
            let strategy = ary.shift();  
            //把input的value添加進參數列表
            ary.unshift( dom.value ); 
            //把errorMsg添加進參數列表
            ary.push( errorMsg ); 
            //委託策略對象調用
            return strategies[ strategy ].apply( dom, ary ); 
        })
    }
    start(){
        for ( var i = 0,validatorFunc; validatorFunc = this.cache[i++];) {
            var msg = validatorFunc(); //開始校驗,並取得校驗後的返回信息
            if ( msg ) {  //若是msg存在,則說明校驗不經過
                return msg; 
            }
        }
    }
}
複製代碼

在上述中,經過對業務規則這種算法的抽象,經過策略模式來完成表單檢驗,在修改某個校驗規則的時候,咱們只有修改少許代碼便可。好比想把用戶名的輸入改爲不能少於4個字符,只須要把minLength:6改成minLength:4便可

優化二(多個校驗規則)

目前實現的表單校驗有一點小問題:一個文本輸入框只能對應一種校驗規則。 若是想要添加多種檢驗規則,能夠經過如下方式添加:

validator.add( registerForm.userName, [{
    strategy: 'isNonEmpty',
    errorMsg: '用戶名不能爲空'
},{
    strategy: 'minLength:10',
    errorMsg: '用戶名長度不能小於10位'
}])
複製代碼

要修改 Validator 中的 add 方法,經過遍歷的方式,把多個檢驗規則添加到cache中。

add (dom, rules) {
    let self = this;
    for (let i = 0,rule; rule = rules[i++];) {
        (function ( rule ) {
            let strategyAry = rule.strategy.split( ':' );
            let errorMsg = rule.errorMsg;
            self.cache.push( function () {
                let strategy = strategyAry.shift();
                strategyAry.unshift( dom.value );
                strategyAry.push( errorMsg );
                return strategies[ strategy ].apply( dom, strategyAry )
            })
        })(rule)
    }
}
複製代碼

總結

優勢:

  • 策略之間相互獨立,但策略能夠自由切換,這個策略模式的特色給策略模式帶來不少靈活性,也提升了策略的複用率;
  • 若是不採用策略模式,那麼在選策略時通常會採用多重的條件判斷,採用策略模式能夠避免多重條件判斷,增長可維護性;
  • 可擴展性好,策略能夠很方便的進行擴展;

缺點:

  • 在策略模式中,咱們會增長不少策略類、策略對象
  • 要使用策略模式,咱們必須瞭解到全部的 strategy 、必須瞭解各個 strategy 之間的不一樣點,才能選擇一個適合的 strategy 。好比,咱們要選擇一種合適的旅遊出行路線,必須先了解選擇飛機、火車、自行車等方案的細節。此時 strategy 要向客戶暴露它的全部實現,這是違反最少知識原則的。

使用場景:

  1. 多個算法只在行爲上稍有不一樣的場景,這時可使用策略模式來動態選擇算法;
  2. 算法須要自由切換的場景;
  3. 有時須要多重條件判斷,那麼可使用策略模式來規避多重條件判斷的狀況;

參考連接

JavaScript設計模式與開發實踐

結語

你的點贊是對我最大的確定,若是以爲有幫助,請留下你的讚揚,謝謝!!!

相關文章
相關標籤/搜索