Javascript策略模式理解以及應用

最近一直在看Javascript設計模式,想經過寫文章來增長本身對策略模式的理解,同時記錄本身學習的經歷。但願你們看完以後以爲有收穫能夠幫忙點個贊表示支持。html

策略模式的定義

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

每次遇到這種設計模式的定義,第一眼的感受老是很懵逼,不知所云。其實有一個辦法:素質三連問。咱們能夠把定義細化,而後分析對應每一個字段的含義,組合起來,就能明白定義的真正想表達的意思。設計模式

因此針對策略模式的定義咱們就能夠來一波素質三連問:bash

  1. 這裏的算法是指什麼
  2. 爲啥須要一個個封裝起來
  3. 相互替換又是指啥

在回答素質三連問以前,咱們能夠從生活中入手來看策略模式的應用場景。其實不少場景咱們均可以使用到不一樣到策略來解決問題。app

旅遊dom

  1. 若是你是土豪或者時間緊,能夠選擇搭飛機
  2. 若是你是小資生活,也不趕時間,能夠選擇搭高鐵
  3. 若是你是窮遊,那你能夠選擇騎自行車等等

壓縮文件函數

  1. zip算法
  2. gzip算法

由此能夠看出,其實咱們的策略就是解決咱們的問題的一種方法,這種方法咱們能夠定義爲一種算法。post

策略模式的應用

講了這麼多,其實你們最關心的仍是策略模式的應用場景。接下來咱們用年終獎的例子爲你們一步一步解決咱們的素質三連問。年終獎是根據員工的工資基數以及年末績效狀況來發放的。如:績效爲S的年終獎有4倍工資、績效爲A的年終獎有3倍工資,績效爲B的年終獎只能有2倍的工資。這種邏輯咱們能夠用基本代碼實現學習

var calculateBonus = function (performanceLevel, salary) {
    if (performanceLevel === 'S') {
        return salary * 4;
    };
    if (performanceLevel === 'A') {
        return salary * 3;
    };
    if (performanceLevel === 'B') {
        return salary * 2;
    };
}

//得到績效爲B的員工
calculateBonus('B', 20000);
//得到績效爲S的員工
calculateBonus('S', 6000);
複製代碼

顯而易見,這段代碼雖然實現了咱們想要的功能,可是很侷限,有如下幾種缺點優化

  1. 包含了過多的if-else語句,使函數過於龐大
  2. calculateBonus函數缺少彈性,若是須要新增長不一樣的績效等級,須要更改其內部實現,違反了開放-封閉原則
  3. 算法複用性差,若是其餘地方須要用到這種計算規則,只能從新輸出(複製、粘貼)

優化一(組合函數)

咱們能夠經過組合函數來重構這段代碼,把各類算法(即年終獎的計算規則)封裝到一個個獨立的小函數中,同時給個良好的命名,那麼咱們就能夠解決上面的缺點3算法複用的問題。代碼以下

var performanceS = function (salary) {
    return salary * 4;
};
var performanceA = function (salary) {
    return salary * 3;
};
var performanceB = function (salary) {
    return salary * 2;
};

var calculateBonus = function ( performanceLevel, salary) {
    if ( performanceLevel === 'S' ) {
        return performanceS( salary );
    };
    if ( performanceLevel === 'A' ) {
        return performanceA( salary );
    };
    if ( performanceLevel === 'B' ) {
        return performanceB( salary );
    };
}

calculateBonus( 'A', 10000 );

複製代碼

經過組合函數,咱們能夠看出咱們的算法被封裝成一個個小函數,從而能夠解決函數複用的問題。可是咱們的核心問題,也就是上述的缺點一、缺點2並無解決,以此咱們繼續進行改進,此次經過策略模式來優化。

優化二(策略模式)

首先咱們應該瞭解將不變的部分和變化的部分隔開是每一個設計模式的主題,而策略模式的目的就是將算法的使用和算法的實現分離開來。那麼在此例子中,算法的使用方式是不變的,根據某個算法計算獎金數額,可是算法的實現是可變的,如績效S、A的實現方式。

策略模式的組成:

  1. 策略類,策略類封裝了具體的算法(績效的計算方式),並負責具體的計算過程。
  2. 環境類(Context),Context接受客戶的請求,隨後把請求委託給某一個策略類。
  3. 橋樑,Context中要維持對某個策略對象的引用
//策略類(S)
var performanceS = function () {}
//算法S內部具體實現
performanceS.prototype.calculate = function ( salary ) {
    return salary * 4;
}
//策略類(A)
var performanceA = function () {}
//算法A內部具體實現
performanceA.prototype.calculate = function ( salary ) {
    return salary * 3;
}
//策略類(B)
var performanceB = function () {}
//算法B內部具體實現
performanceB.prototype.calculate = function ( salary ) {
    return salary * 2;
}
複製代碼

接下來定義環境類,這裏指的就是咱們的獎金類Bonus

var Bonus = function () {
    this.salary = null;     //原始工資
    this.strategy = null;  //績效公司對應的策略對象
}

Bonus.prototype.setSalary = function (salary) {
    this.salary = salary;  //設置原始工資
};

Bonus.portotype.setStrategy = function (strategy) {
    this.strategy = strategy; //設置員工績效等級對應的策略對象
}

Bonus.prototype.getBonus = function () { //取得獎金數額
    //維持對策略對象的引用
    return this.strategy.calculate( this.salary );  //委託給對應的策略對象
}
複製代碼

獎金類的調用

var bonus = new Bonus();

bonus.setSalary( 10000 );
bonus.setStrategy( new performanceS() ); //設置策略對象

console.log( bonus.getBonus() ); //把請求委託給了以前保存好的策略對象
複製代碼

以上的例子展現了策略模式的應用,使代碼變得更加清晰,各個類職責更加鮮明,也解決了以上普通函數調用的缺點1、缺點二。那麼這種類的實現實際上是基於傳統的面嚮對象語言模仿的,所以咱們能夠進一步對這段代碼進行優化,變成JavaScript版本的策略模式

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

爲何JavaScript版本的策略模式跟傳統的面嚮對象語言的策略模式不一樣呢,實際上在JavaScript語言中,函數也是對象,因此能夠直接把strategy類直接定義爲函數。代碼以下:

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

一樣,咱們也能夠直接用calculateBonus函數充當Context來接受用戶的請求,並不須要Bonus類來表示

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

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

從以上的例子咱們其實已經回答了策略模式的定義中的素質三連問,策略模式的算法是指的什麼(績效的計算方法)、爲何要封裝(可複用)、相互替換又是指啥(績效可發生變化、可是不影響函數的調用,只需改變參數)

延伸擴展

上述一直在定義策略模式中算法的概念,實際開發中,咱們一般能夠把算法的含義擴散開來,使得策略模式也能夠用來封裝一系列的'業務規則'。只要這些業務規則指向的目標一致,而且能夠被替換使用,咱們就能夠用策略模式來封裝它們 那麼在咱們的'業務規則'中,表單的校驗就符合咱們使用策略模式。

表單驗證

假設咱們正在編寫一個註冊頁面,在點擊按鈕以前,有以下幾條校驗規則:

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

根據這樣的要求,在咱們沒有引入策略模式以前,咱們能夠經過以下代碼編寫

<html>
    <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>
複製代碼

這樣的代碼一樣有着跟上述年終獎同樣的缺點,函數過於龐大、缺少彈性以及複用性差,那麼咱們學了策略模式,確定須要對這種狀況進行優化

優化一

  1. 明確在此場景中,算法具體是什麼,很明顯能夠看出,這裏的算法指的就是咱們表單驗證邏輯的業務規則。所以咱們能夠把這些業務規則封裝成相對應的策略對象:
var 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;
        }
    ]
}
複製代碼

實現了策略對象的算法,那麼咱們還須要一個環境類來負責接受用戶的請求並委託給strategy對象。可是在咱們實現以前,咱們須要明白環境類與策略對象直接的橋樑是怎麼樣的,也就是用戶是如何向validator類發送請求的。這樣能夠方便咱們實現環境類,也就是這裏的Validator類。 以下是咱們用戶向validator類發送請求的代碼:

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類代碼

var validator = function () {
    this.cache = [];  //保存校驗規則
};
//添加檢驗規則函數
validator.prototype.add = function (dom, rule, errorMsg) {
    //把strategy和參數分開'minLength:6''minLength:6' -> ["minLength", "6"]
    var ary = rule.split(':'); 
    this.cache.push ( function () {
        var strategy = ary.shift(); //用戶挑選的strategy ["minLength", "6"] -> 'minLength' 
        ary.unshift( dom.value ); //把input的value添加進參數列表
        ary.push( errorMsg ); //把errorMsg添加進參數列表
        return strategies[ strategy ].apply( dom, ary ); //委託策略對象調用
    })
}
//檢驗開始函數
Validator.prototype.start = function () {
    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中。

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

策略模式的優缺點

從上述的例子中,很明顯能總結出策略模式的優勢

  1. 採用組合、委託和多態等技術和思想、有效避免了多重條件選擇語句
  2. 採用了開放-封閉原則,將算法封裝在獨立的strategy中,易於理解、切換、拓展
  3. 策略模式中的算法能夠進行復用,從而避免不少地方的複製粘貼

同時策略模式也有其缺點,可是並不影響咱們對策略模式的使用

  1. 在策略模式中,咱們會增長不少策略類、策略對象
  2. 要使用策略模式,咱們必須瞭解到全部的strategy、必須瞭解各個strategy之間的不一樣點,才能選擇一個適合的strategy。

結語

你們看完以後,若是以爲有啥不對的地方,請你們提出建議。也但願這篇文章若是對你有幫助,請你們多多點贊、轉發支持!

文章借鑑於:曾探老師的《JavaScript設計模式與開發實踐

相關文章
相關標籤/搜索