JavaScript 设计模式(中)——2.策略模式

2 策略模式

策略模式定义: 定义一系列的算法,把它们一个个封装起来,并且使它们可以相互替换;

2.1 策略模式

策略模式的目的就是将算法的使用与算法的实现分离开来,将不变的部分和变化的部分隔开是每个设计模式的主题;

一个基于策略模式的程序至少由两部分组成。第一个部分是一组策略类,策略类封装了具体的算法,并负责具体的计算过程。 第二个部分是环境类 Context, Context 接受客户的请求,随后把请求委托给某一个策略类。要做到这点,说明 Context 中要维持对某个策略对象的引用;

实例:积分模式(基础分乘上对于的等级倍数)

// 1. 最简单代码实现
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. 模仿传统面向对象语言中的策略模式实现
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; // 等级对应的策略对象
};
Bonus.prototype.setSalary = function( base ){
  this.base = base; // 设置基础值
};
Bonus.prototype.setStrategy = function( strategy ){
  this.strategy = strategy; // 设置等级对应的策略对象
};
Bonus.prototype.getBonus = function(){ // 取得总分数
  return this.strategy.calculate( this.base ); // 把计算分数的操作委托给对应的策略对象
};

2.2 JavaScript 版本的策略模式

在 JavaScript 语言中,函数也是对象,所以更简单和直接的做法是把 strategy 直接定义为函数:

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 策略模式实现表单校验

// 步骤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. 实现 Validator 类(作为 Context,负责接收用户的请求并委托给 strategy 对象)
var Validator = function(){
  this.cache = []; // 保存校验规则
};
Validator.prototype.add = function( dom, rule, errorMsg ){
  var ary = rule.split( ':' ); // 把 strategy 和参数分开
  this.cache.push(function(){ // 把校验的步骤用空函数包装起来,并且放入 cache
    var strategy = ary.shift(); // 用户挑选的 strategy
    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 ){ // 如果有确切的返回值,说明校验没有通过
      return msg;
    }
  }
};
// 步骤3. 向 Validator 类发送校验的请求
var validataFunc = function(){
  var validator = new Validator(); // 创建一个 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(); // 如果 errorMsg 有确切的返回值,说明未通过校验
  if ( errorMsg ){
    alert ( errorMsg );
    return false; // 阻止表单提交
  }
};

2.4 策略模式的优缺点

策略模式的优点:

  • 策略模式利用组合、委托和多态等技术和思想,可以有效地避免多重条件选择语句;
  • 策略模式提供了对开放—封闭原则的完美支持,将算法封装在独立的 strategy 中,使得它们易于切换,易于理解,易于扩展;
  • 策略模式中的算法也可以复用在系统的其他地方,从而避免许多重复的复制粘贴工作;
  • 在策略模式中利用组合和委托来让 Context 拥有执行算法的能力,这也是继承的一种更轻便的替代方案;

策略模式的缺点:

  • 使用策略模式会在程序中增加许多策略类或者策略对象,但实际上这比把它们负责的逻辑堆砌在 Context 中要好;
  • 使用策略模式,必须了解所有的 strategy,必须了解各个 strategy 之间的不同点,这样才能选择一个合适的 strategy ;由于 strategy 要向客户暴露它的所有实现,这是违反最少知识原则;

2.5 一等函数对象与策略模式

之前的策略模式示例中,既有模拟传统面向对象语言的版本,也有针对 JavaScript 语言的特有实现。在以类为中心的传统面向对象语言中,不同的算法或者行为被封装在各个策略类中, Context 将请求委托给这些策略对象,这些策略对象会根据请求返回不同的执行结果,这样便能表现出对象的多态性;

在 JavaScript 中,除了使用类来封装算法和行为之外,直接使用函数也是一种选择。这些“算法”可以被封装到函数中并且四处传递,也就是常说的“高阶函数”。实际上在 JavaScript 这种将函数作为一等对象的语言里,策略模式已经融入到了语言本身当中,我们经常用高阶函数来封装不同的行为,并且把它传递到另一个函数中。当我们对这些函数发出“调用”的消息时,不同的函数会返回不同的执行结果;

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 策略模式小结

本小节既有接近传统面向对象语言的策略模式实现,也有更适合 JavaScript 语言的策略模式版本。在 JavaScript 语言的策略模式中,策略类往往被函数所代替,这时策略模式就成为一种“隐形”的模式。

系列链接

  1. JavaScript 设计模式(上)——基础知识
  2. JavaScript 设计模式(中)——1.单例模式
  3. JavaScript 设计模式(中)——2.策略模式
  4. JavaScript 设计模式(中)——3.代理模式
  5. JavaScript 设计模式(中)——4.迭代器模式
  6. JavaScript 设计模式(中)——5.发布订阅模式
  7. JavaScript 设计模式(中)——6.命令模式
  8. JavaScript 设计模式(中)——7.组合模式
  9. JavaScript 设计模式(中)——8.模板方法模式
  10. JavaScript 设计模式(中)——9.享元模式
  11. JavaScript 设计模式(中)——10.职责链模式
  12. JavaScript 设计模式(中)——11. 中介者模式
  13. JavaScript 设计模式(中)——12. 装饰者模式
  14. JavaScript 设计模式(中)——13.状态模式
  15. JavaScript 设计模式(中)——14.适配器模式
  16. JavaScript 设计模式(下)——设计原则
  17. JavaScript 设计模式练习代码

本文主要参考了《JavaScript设计模式和开发实践》一书

你可能感兴趣的:(JavaScript 设计模式(中)——2.策略模式)