JavaScript 設(shè)計模式(中)——2.策略模式

2 策略模式

策略模式定義: 定義一系列的算法,把它們一個個封裝起來,并且使它們可以相互替換;

2.1 策略模式

策略模式的目的就是將算法的使用與算法的實現(xiàn)分離開來,將不變的部分和變化的部分隔開是每個設(shè)計模式的主題;

一個基于策略模式的程序至少由兩部分組成。第一個部分是一組策略類,策略類封裝了具體的算法,并負(fù)責(zé)具體的計算過程。 第二個部分是環(huán)境類 Context, Context 接受客戶的請求,隨后把請求委托給某一個策略類。要做到這點,說明 Context 中要維持對某個策略對象的引用;

實例:積分模式(基礎(chǔ)分乘上對于的等級倍數(shù))

// 1. 最簡單代碼實現(xiàn)
var calculateBonus = function( level, base ){
  if ( level === 'S' ){ return base * 4; }
  if ( level === 'A' ){ return base * 3; }
};
calculateBonus( 'B', 10 ); // 輸出: 40
calculateBonus( 'S', 5 ); // 輸出: 15

// 1. 模仿傳統(tǒng)面向?qū)ο笳Z言中的策略模式實現(xiàn)
var performanceS = function(){};
performanceS.prototype.calculate = function( base ){
  return base * 4;
};
var performanceA = function(){};
  performanceA.prototype.calculate = function( base ){
return base * 3;
};
// 定義總分類 Bonus:
var Bonus = function(){
  this.base = null; // 原始值
  this.strategy = null; // 等級對應(yīng)的策略對象
};
Bonus.prototype.setSalary = function( base ){
  this.base = base; // 設(shè)置基礎(chǔ)值
};
Bonus.prototype.setStrategy = function( strategy ){
  this.strategy = strategy; // 設(shè)置等級對應(yīng)的策略對象
};
Bonus.prototype.getBonus = function(){ // 取得總分?jǐn)?shù)
  return this.strategy.calculate( this.base ); // 把計算分?jǐn)?shù)的操作委托給對應(yīng)的策略對象
};

2.2 JavaScript 版本的策略模式

在 JavaScript 語言中,函數(shù)也是對象,所以更簡單和直接的做法是把 strategy 直接定義為函數(shù):

var strategies = {
  "S": function( base ){ return base * 4; },
  "A": function( base ){ return base * 3; },
};
var calculateBonus = function( level, base ){
  return strategies[ level ]( base );
};
console.log( calculateBonus( 'S', 10 ) ); // 輸出: 40
console.log( calculateBonus( 'A', 5 ) ); // 輸出: 15

2.3 策略模式實現(xiàn)表單校驗

// 步驟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;
    }
  }
};
// 步驟2. 實現(xiàn) Validator 類(作為 Context,負(fù)責(zé)接收用戶的請求并委托給 strategy 對象)
var Validator = function(){
  this.cache = []; // 保存校驗規(guī)則
};
Validator.prototype.add = function( dom, rule, errorMsg ){
  var ary = rule.split( ':' ); // 把 strategy 和參數(shù)分開
  this.cache.push(function(){ // 把校驗的步驟用空函數(shù)包裝起來,并且放入 cache
    var strategy = ary.shift(); // 用戶挑選的 strategy
    ary.unshift( dom.value ); // 把 input 的 value 添加進參數(shù)列表
    ary.push( errorMsg ); // 把 errorMsg 添加進參數(shù)列表
    return strategies[ strategy ].apply( dom, ary );
  });
};
Validator.prototype.start = function(){
  for ( var i = 0, validatorFunc; validatorFunc = this.cache[ i++ ]; ){
    var msg = validatorFunc(); // 開始校驗,并取得校驗后的返回信息
    if ( msg ){ // 如果有確切的返回值,說明校驗沒有通過
      return msg;
    }
  }
};
// 步驟3. 向 Validator 類發(fā)送校驗的請求
var validataFunc = function(){
  var validator = new Validator(); // 創(chuàng)建一個 validator 對象
  /***************添加一些校驗規(guī)則****************/
  validator.add( registerForm.userName, 'isNonEmpty', '用戶名不能為空' );
  validator.add( registerForm.password, 'minLength:6', '密碼長度不能少于 6 位' );
  validator.add( registerForm.phoneNumber, 'isMobile', '手機號碼格式不正確' );
  var errorMsg = validator.start(); // 獲得校驗結(jié)果
  return errorMsg; // 返回校驗結(jié)果
}

var registerForm = document.getElementById( 'registerForm' );
registerForm.onsubmit = function(){
  var errorMsg = validataFunc(); // 如果 errorMsg 有確切的返回值,說明未通過校驗
  if ( errorMsg ){
    alert ( errorMsg );
    return false; // 阻止表單提交
  }
};

2.4 策略模式的優(yōu)缺點

策略模式的優(yōu)點:

  • 策略模式利用組合、委托和多態(tài)等技術(shù)和思想,可以有效地避免多重條件選擇語句;
  • 策略模式提供了對開放—封閉原則的完美支持,將算法封裝在獨立的 strategy 中,使得它們易于切換,易于理解,易于擴展;
  • 策略模式中的算法也可以復(fù)用在系統(tǒng)的其他地方,從而避免許多重復(fù)的復(fù)制粘貼工作;
  • 在策略模式中利用組合和委托來讓 Context 擁有執(zhí)行算法的能力,這也是繼承的一種更輕便的替代方案;

策略模式的缺點:

  • 使用策略模式會在程序中增加許多策略類或者策略對象,但實際上這比把它們負(fù)責(zé)的邏輯堆砌在 Context 中要好;
  • 使用策略模式,必須了解所有的 strategy,必須了解各個 strategy 之間的不同點,這樣才能選擇一個合適的 strategy ;由于 strategy 要向客戶暴露它的所有實現(xiàn),這是違反最少知識原則;

2.5 一等函數(shù)對象與策略模式

之前的策略模式示例中,既有模擬傳統(tǒng)面向?qū)ο笳Z言的版本,也有針對 JavaScript 語言的特有實現(xiàn)。在以類為中心的傳統(tǒng)面向?qū)ο笳Z言中,不同的算法或者行為被封裝在各個策略類中, Context 將請求委托給這些策略對象,這些策略對象會根據(jù)請求返回不同的執(zhí)行結(jié)果,這樣便能表現(xiàn)出對象的多態(tài)性;

在 JavaScript 中,除了使用類來封裝算法和行為之外,直接使用函數(shù)也是一種選擇。這些“算法”可以被封裝到函數(shù)中并且四處傳遞,也就是常說的“高階函數(shù)”。實際上在 JavaScript 這種將函數(shù)作為一等對象的語言里,策略模式已經(jīng)融入到了語言本身當(dāng)中,我們經(jīng)常用高階函數(shù)來封裝不同的行為,并且把它傳遞到另一個函數(shù)中。當(dāng)我們對這些函數(shù)發(fā)出“調(diào)用”的消息時,不同的函數(shù)會返回不同的執(zhí)行結(jié)果;

var S = function( salary ){ return salary * 4; };
var A = function( salary ){ return salary * 3; };
var B = function( salary ){ return salary * 2; };
var calculateBonus = function( func, salary ){
  return func( salary );
};
calculateBonus( S, 10 ); // 輸出: 40

2.6 策略模式小結(jié)

本小節(jié)既有接近傳統(tǒng)面向?qū)ο笳Z言的策略模式實現(xiàn),也有更適合 JavaScript 語言的策略模式版本。在 JavaScript 語言的策略模式中,策略類往往被函數(shù)所代替,這時策略模式就成為一種“隱形”的模式。

系列鏈接

  1. JavaScript 設(shè)計模式(上)——基礎(chǔ)知識
  2. JavaScript 設(shè)計模式(中)——1.單例模式
  3. JavaScript 設(shè)計模式(中)——2.策略模式
  4. JavaScript 設(shè)計模式(中)——3.代理模式
  5. JavaScript 設(shè)計模式(中)——4.迭代器模式
  6. JavaScript 設(shè)計模式(中)——5.發(fā)布訂閱模式
  7. JavaScript 設(shè)計模式(中)——6.命令模式
  8. JavaScript 設(shè)計模式(中)——7.組合模式
  9. JavaScript 設(shè)計模式(中)——8.模板方法模式
  10. JavaScript 設(shè)計模式(中)——9.享元模式
  11. JavaScript 設(shè)計模式(中)——10.職責(zé)鏈模式
  12. JavaScript 設(shè)計模式(中)——11. 中介者模式
  13. JavaScript 設(shè)計模式(中)——12. 裝飾者模式
  14. JavaScript 設(shè)計模式(中)——13.狀態(tài)模式
  15. JavaScript 設(shè)計模式(中)——14.適配器模式
  16. JavaScript 設(shè)計模式(下)——設(shè)計原則
  17. JavaScript 設(shè)計模式練習(xí)代碼

本文主要參考了《JavaScript設(shè)計模式和開發(fā)實踐》一書

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容