【Javascript】设计模式之策略模式

本文讲解了如何使用策略模式在JavaScript中优化奖金计算函数和表单验证,提升代码灵活性和可扩展性。
摘要由CSDN通过智能技术生成

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

1、使用策略模式计算奖金

假如有个需求:
绩效为 S 的人年终奖有 4 倍工资,绩效为 A 的人年终奖有 3 倍工资,而绩效为 B 的人年终奖是 2 倍工资

1.1 最初代码实现:

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

缺点:

  • calculateBonus 函数,包含了很多 if-else 语句,这些语句需要覆盖所有的逻辑分支
  • calculateBonus 函数缺乏弹性,如果增加了一种新的绩效等级 C,或者想把绩效 S 的奖金
    系数改为 5,那我们必须深入 calculateBonus 函数的内部实现,这是违反开放封闭原则的
  • 算法的复用性差,如果在程序的其他地方需要重用这些计算奖金的算法呢?我们的选择
    只有复制和粘贴

1.2 使用组合函数重构代码
使用组合函数来重构代码,我们把各种算法封装到一个个的小函数里面

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);
    }
}

console.log(calculateBonus('A', 2000));

问题:calculateBonus 函数有可能越来越庞大,而且在系统变化的时候缺乏弹性

1.3 使用策略模式重构代码
一个基于策略模式的程序至少由两部分组成:
第一个部分是一组策略类,策略类封装了具体的算法,并负责具体的计算过程。
第二个部分是环境类 Context,Context 接受客户的请求,随后把请求委托给某一个策略类

// 定义绩效的计算规则
var performanceS = function () { };
performanceS.prototype.calculate = function (salary) {
    return salary * 4;
}
var performanceA = function () { };
performanceA.prototype.calculate = function (salary) {
    return salary * 3;
}
var performanceB = function () { };
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.prototype.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() ); // 输出:40000 
bonus.setStrategy( new performanceA() ); // 设置策略对象
console.log( bonus.getBonus() ); // 输出:30000 

2、JavaScript 版本的策略模式

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

var strategies = {
    "S": function (salary) {
        return salary * 4;
    },
    "A": function (salary) {
        return salary * 3;
    },
    "B": function (salary) {
        return salary * 2;
    },
}

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

console.log(calculateBonus('S', 3000)); // 12000
console.log(calculateBonus('B', 1000)); // 2000

3、应用:表单验证

做如下表单验证:

  • 用户名不能为空。
  • 密码长度不能少于 6 位。
  • 手机号码必须符合格式

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;
    }
  },
};

Validator 类的实现:

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;
    }
  }
};

测试:

var validator = new Validator();
validator.add({ value: '' }, 'isNonEmpty', '用户名不能为空');
validator.add({ value: '1234' }, 'minLength:6', '密码长度不能少于 6 位');
var errorMsg = validator.start();
console.log(errorMsg)

3.2 给某个文本输入框添加多种校验规则

/***********************策略对象**************************/
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;
    }
  },
};
/***********************Validator 类**************************/
var Validator = function () {
  this.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);
  }
};
Validator.prototype.start = function () {
  for (var i = 0, validatorFunc; (validatorFunc = this.cache[i++]); ) {
    var errorMsg = validatorFunc();
    if (errorMsg) {
      return errorMsg;
    }
  }
};

/***********************客户调用代码**************************/
var validataFunc = function () {
  var validator = new Validator();
  validator.add({ value: '' }, [
    {
      strategy: 'isNonEmpty',
      errorMsg: '用户名不能为空',
    },
    {
      strategy: 'minLength:6',
      errorMsg: '用户名长度不能小于 10 位',
    },
  ]);
  validator.add({ value: '123' }, [
    {
      strategy: 'minLength:6',
      errorMsg: '密码长度不能小于 6 位',
    },
  ]);
  validator.add({ value: '1255555555' }, [
    {
      strategy: 'isMobile',
      errorMsg: '手机号码格式不正确',
    },
  ]);
  var errorMsg = validator.start();
  return errorMsg;
};

var errorMsg = validataFunc();
if (errorMsg) {
  console.warn(errorMsg);
  return false;
}

4、策略模式的优缺点

优点

  • 避免多重条件选择语句
  • 策略模式提供了对开放—封闭原则的完美支持,将算法封装在独立的 strategy 中,使得它们易于切换,易于理解,易于扩展
  • 策略模式中的算法也可以复用在系统的其他地方,从而避免许多重复的复制粘贴工作

缺点:

  • 使用策略模式会在程序中增加许多策略类或者策略对象
  • 要使用策略模式,必须了解所有的 strategy,必须了解各个 strategy 之间的不同点,这样才能选择一个合适的 strategy
  • 24
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值