TIP

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

# 案例:年终奖的计算

案例一:年终奖的计算 年终奖是根据员工的工资基数和年底绩效情况来发的,绩效为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
	}
}

calculateBonus('B', 20000) // 输出40000
calculateBonus('S', 6000) // 输出24000

上述代码存在三个

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

# 策略模式重构代码

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
}

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

上述代码经过策略模式重构之后,代码变得更加清晰,各个类的职责更加鲜明。但是这段代码是基于传统面向对象语言的模仿,非常冗余。

# JavaScript版本的策略模式

在JavaScript中,函数也是对象。上述计算年终奖的例子,更简单和直接的做法是把strategy直接定义为函数。

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

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

console.log(calculateBonus("S",4000));
console.log(calculateBonus("A",2000));

# 更广义的“算法”

从定义上看,策略模式就是用来封装算法的。但实际开发中,我们也可以用策略模式来封装一系列的“业务规则”。只要这些业务规则指向的目标一致,并且可以被替换使用,我们就可以用策略模式来封装它们。

# 案例:表单校验

假设我们正在编写一个注册页面,在点击注册按钮之前,有如下几条校验逻辑

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

# 不使用策略模式的版本

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

# 使用策略模式重构的版本

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

var Validator = function(){
  this.cache = []; // 保存校验规则
};

Validator.prototype.add = function (dom, rule, errorMsg) { 
  var ary = rule.split(':'); 
  this.cache.push(function(){ //
    var strategy = ary.shift(); 
    ary.unshift(dom.value); 
    ary.push(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 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; // 阻止表单提交 
  }
};

# 策略模式的优缺点

策略模式的优点:

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

策略模式的缺点

  • 使用策略模式会在程序中增加许多策略类或者策略对象
  • 使用策略模式时,所有的strategy要向客户暴露它的所有实现,违反最少知识原则

# 小结

在JavaScript的策略模式中,策略类往往被函数所代替,这时策略模式就成为一种“隐形”的模式。

# 心得体会

利用策略模式来减少if语句,装逼神器啊。😂😂😂😂😂😂