文章目录
- 1、使用策略模式计算奖金
- 2、JavaScript 版本的策略模式
- 3、应用:表单验证
- 3.1 用策略模式进行表单验证
- 3.2 给某个文本输入框添加多种校验规则
- 4、策略模式的优缺点
策略模式的定义是:定义一系列的算法,把它们一个个封装起来,并且使它们可以相互替换
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 () {// 把校验的步骤用空函数包装起来,并且放入 cachevar strategy = ary.shift(); // 用户挑选的 strategyary.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