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语句,装逼神器啊。😂😂😂😂😂😂