声明 本篇中代码和部分描述参考和摘录自JavaScript设计模式与实战一书
很多公司的年终奖都是根据员工的工资基数和年底绩效来发放的。比如,某个公司的绩效和奖金对应关系如下:
javascriptvar calculateBonus = function(level, salary) {
if (level === 'S') {
return 4 * salary;
}
if (level === 'A') {
return 3 * salary;
}
if (level === 'B') {
return 2 * salary;
}
}
calculateBonus('S', 20000); // 输出80000
calculateBonus('B', 10000); // 输出20000
上面代码的错误显而易见
javascriptvar performanceS = function(salary) {
return salary * 4;
};
var performanceA = function(salary) {
return salary * 3;
};
var performanceB = function(salary) {
return salary * 2;
};
var calculateBonus = function(level, salary) {
if (level === 'S') {
return performanceS(salary);
}
if (level === 'A') {
return performanceA(salary);
}
if (level === 'B') {
return performanceB(salary);
}
};
calculateBonus('A', 10000); // 输出30000
我们的程序得到了一点改善,但是我们还是没有解决最重要的问题:calculateBonus函数会越来越大,并且随着系统的变化缺乏弹性。
策略模式指的是定义一系列的算法,把它们一个个封装起来。在这里,我们使用策略模式将算法的使用和算法本身分离开来。
javascriptvar 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; // 要执行的策略
}
/**
* 设置工资基数
* @param {Number} salary
*/
Bonus.prototype.setSalary = function(salary) {
this.salary = salary;
}
/**
* 设置策略
* @param {Object} strategy
*/
Bonus.prototype.setStrategy = function(strategy) {
this.strategy = strategy;
}
/**
* 计算奖金
* @return {Number} 奖金数
*/
Bonus.prototype.getBonus = function() {
return this.strategy.calculate(this.salary);
}
var bonus = new Bonus();
bonus.setSalary(5000);
bonus.setStrategy(new performanceS());
console.log(bonus.getBonus()); // 输出20000
bonus.setStrategy(new performanceA());
console.log(bonus.getBonus()); // 输出15000
可以看到使用策略模式后,代码变得更加清晰,各个类的职责也更加鲜明。
javascriptvar 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', 10000)); // 输出4000
PeterNoring在他的演讲中曾说过:“在函数作为一等对象的语言中,策略模式是隐形的。strategy就是值为函数的变量。”在JavaScript中,除了用类来封装算法,使用函数当然也是一种选择。这些“算法”被封装到函数中并且四处传递,也就是我们常说的“高阶函数”。根据这一原理,我们对代码可以做进一步的改进。
javascriptvar S = function(salary) {
return salary * 4;
};
var A = function(salary) {
return salary * 3;
};
var B = function(salary) {
return salary * 2;
};
var calculateBonus = function(func, salary) {
return func(salary);
};
console.log(calculateBonus(S, 10000)); // 输出40000
在web项目中,注册、登录、修改用户信息等都离不开表单。在将用户输入的数据提交到后台之前,我们常常会在客户端做一些力所能及的校验。 假设我们正在编写一个注册的页面,注册表单有以下的几条校验规则。
先来看看我们通常的做法:
html<html>
<head>
<meta charset="utf8">
<title>JavaScript设计模式 - 策略模式</title>
<style>
.form-group {
margin-bottom: 10px;
}
</style>
</head>
<body>
<div class="wrapper">
<form action="" id="form-signup" method="post">
<div class="form-group">
<label for="username">用户名</label>
<input type="text" name="username" />
</div>
<div class="form-group">
<label for="password">密码</label>
<input type="password" name="password" />
</div>
<div class="form-group">
<label for="phone">手机号</label>
<input type="tel" name="phone" />
</div>
<button type="submit">提交</button>
</form>
</div>
<script type="text/javascript">
var formSignUp = document.getElementById('form-signup');
formSignUp.onsubmit = function() {
if (formSignUp.elements.username.value === '') {
alert('用户名不能为空');
return false;
}
if (formSignUp.elements.password.value.length < 6) {
alert('密码长度不能小于6');
return false;
}
if (!/(^1[3|5|8|7][0-9]{9}$)/.test(formSignUp.elements.phone.value)) {
alert('非法的手机号');
return false;
}
return true;
}
</script>
</body>
</html>
上面的代码的缺点,跟我们计算奖金的第一个版本的代码一样:
根据经验,我们把这些校验规则都封装成策略对象。
javascriptvar 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|7|8][0-9]{9}$/.test(value)) {
return errorMsg;
}
}
};
接下来我们实现一个Validator类,Validator类负责接收用户委托的strategy对象。我们首先看看它的使用方法。
javascriptvar validateFunc = function() {
var validator = new Validator();
// 添加验证规则
validator.add(formSignUp.elements.username, 'isNonEmpty', '用户名不能为空');
validator.add(formSignUp.elements.password, 'minLength:6', '密码长度不能小于6');
validator.add(formSignUp.elements.phone, 'isMobile', '非法的手机号');
return validator.start();
};
var formSignUp = document.getElementById('form-signup');
formSignUp.onsubmit = function() {
var errorMsg = validateFunc();
if (errorMsg) {
alert(errorMsg);
return false; // 校验错误则阻止表单的提交
}
};
从上面的代码我们看到,我们初始化了一个validator对象,并且通过validator.add()方法添加一些校验规则。validator.add方法接收3各个参数,以下面的代码进行说明。
javascriptvalidator.add(formSignUp.elements.password, 'minLength:6', '密码长度不能小于6');
当我们往validator对象里添加完规则后,我们会调用validator.start()方法来启动校验。如果校验返回了一个errorMsg,说明校验没有通过,此时需要让formSignUp.onsubmit方法返回false来阻止表单提交。
Validator类的实现:
javascriptvar Validator = function() {
this.cache = [];
};
Validator.prototype.add = function(el, rule, errorMsg) {
var arr = rule.split(':'); // 把strategy和参数分开
this.cache.push(function() { // 将校验步骤用空函数包起来,并且放入cache中
var strategy = arr.shift(); // 用户挑选的strategy
arr.unshift(el.value);
arr.push(errorMsg);
return strategies[ strategy ].apply(el, arr);
});
};
Validator.prototype.start = function() {
for (var i =0, validatorFunc; validatorFunc =this.cache[ i++ ]; ) {
var msg = validatorFunc();
if (msg) {
return msg;
}
}
};
上面的代码我们只是专注在策略模式的使用上,因此存在小小的缺陷。比如我们要给用户名输入框添加一个新的校验规则只能通过下面的做法。
javascriptvalidator.add(formSignUp.elements.username, 'isNonEmpty', '用户名不能为空');
// 添加新的规则
validator.add(formSignUp.elements.username, 'minLength:4', '用户名长度不能小于4');
有没有更好的办法呢,留给大家去思考。
本文作者:谭三皮
本文链接:
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!