最近在看《JavaScript设计模式与开发实践》,一本很不错的书,本次内容整理自该书,以便日后翻阅。
在程序设计中,常常遇到类似的情况,要实现某一个功能有多种方案可以选择。比如一个压缩文件的程序,既可以选择zip算法,也可以选择gzip算法。这些算法灵活多样,而且可以随意互相替换。这种解决方案就是本文将要介绍的策略模式。策略模式是指定义一系列的算法,把它们一个个封装起来,并且使它们可以相互替换。
下面是策略模式运用的两个例子:
1.奖金计算
很多公司的年终奖是根据员工的工资基数和年底绩效情况来发放的。例如,绩效为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
很多人会使用上面那种方法,看上起也并没有什么问题,但其实还是有些缺点的。缺点:该函数比较庞大,包含了很多if-else语句,这些语句需要覆盖所有的逻辑分支;该函数缺乏弹性,如果增加了一种新的绩效等级C,或者想把绩效S的奖金系数改为5,必须深入calculateBonus函数的内部实现,违反开放封闭原则;算法复用性差,如果在程序其他地方需要重用这些计算奖金的算法,只能选择复制粘贴。
运用策略模式,可以实现得更加优雅,如下:
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', 20000 ) ); // 输出:80000
console.log( calculateBonus( 'A', 10000 ) ); // 输出:30000
与前面方法比较,看起来代码量并没有减少,但带来如下改变:消除了那些if else 条件判断语句;如果需要新增一个等级,只需要修改strategies对象一处;复用性更好
2.表单验证
在web项目中,表单验证是比较常见的,后台管理系统尤其多。客户端在把数据提交后台之前常常需要做一些校验,比如注册的时候需要校验是否填写了用户名,密码的长度是否符合规定,等等,这样可以避免因为提交不合法数据而带来的不必要网络开销。
以下图内容为例
假设 字段依次为 title, sort, port, picture(数组),这些都需要客户端做非空验证,标题还需要最大长度限制为20个字。比较常见的实现方式:
if (title === '') {
alert('标题不能为空')
return
}
if (sort === '') {
alert('序号不能为空')
return
}
if (port === '') {
alert('请选择端口')
return
}
if (!picture.length) {
alert('请上传图片')
return
}
运用策略模式实现:
//validator.js
const strategies = {
isNonEmpty: function(value, errorMsg) {
if ( value === '' || value === undefined || value === null || (Array.isArray(value) && !value.length)) {
return errorMsg
}
},
maxLength: function(value, length, errorMsg) {
if ( value.length > length) {
return errorMsg
}
}
}
const Validator = function() {
this.cache = [];
};
Validator.prototype.add = function(value, rules) {
let self = this;
for ( let i = 0, rule; rule = rules[i++];) {
(function(rule) {
let strategyAry = rule.strategy.split(':');
let errorMsg = rule.errorMsg;
self.cache.push(function() {
let strategy = strategyAry.shift();
strategyAry.unshift(value);
strategyAry.push(errorMsg);
return strategies[strategy].apply(value, strategyAry);
})
})(rule)
}
}
Validator.prototype.start = function() {
for (let i = 0, validatorFunc; validatorFunc = this.cache[i++];) {
let errorMsg = validatorFunc();
if (errorMsg) {
return errorMsg
}
}
}
export default Validator;
//页面调用
import Validator from 'validator.js文件路径';
let validator = new Validator();
validator.add(title, [{
strategy: 'isNonEmpty',
errorMsg: '标题不能为空'
},{
strategy: 'maxLength:20',
errorMsg: '标题不能超过20个字'
}])
validator.add(indexNum, [{
strategy: 'isNonEmpty',
errorMsg: '权重不能为空'
}])
validator.add(port, [{
strategy: 'isNonEmpty',
errorMsg: '请选择展示端口'
}])
validator.add(this.picture, [{
strategy: 'isNonEmpty',
errorMsg: '请上传图片'
}])
let errorMsg = validator.start();
if (errorMsg) {
this.$message(errorMsg);
return;
}