设计模式——策略模式
俗话说条条大路通罗马,在程序设计中不少问题也存在着多种解决方案。不同的解决方案虽然都是用来解决同一个问题,但由于场景不同最适合的方案也不一样,于是很自然的想到将解决方案分别封装起来在合适的场景替换使用。这就是策略模式。
算法封装实例
假设我们现在需要为公司提供一段代码来计算不同员工的年终奖,影响因素有员工的工资基数和年底绩效。绩效有S,A,B三个等级,分别对应4,3,2倍工资。
初始实现
这个逻辑非常简单,凭直觉我们很容易就能写出下面这个计算函数。
let calculate = function(level, salary) {
if (level === 'S') {
return salary * 4;
}
if (level === 'A') {
return salary * 3;
}
if (level === 'B') {
return salary * 2;
}
}
虽然功能我们是实现了,但是存在明显缺点。
1.函数内逻辑耦合过紧,如果以后需要增加新的绩效等级,就必须更改函数内部实现。
2.复用性差。如果相同的算法需要用到其他模块中,就必须复制整个函数实现。
策略模式重构
将不变的部分和变化的部分隔离开来是设计模式的核心。我们使用策略模式,来将上述算法的调用和算法内部逻辑隔离开来。
let strategies = {
S(salary) {
return salary * 4;
},
A(salary) {
return salary * 3;
},
B(salary) {
return salary * 2;
}
};
let calculate = function(level, salary) {
return strategies[level](salary);
};
可以看到重构之后代码更加清晰,可拓展性和复用性也更强了。我们将业务逻辑和算法分开封装,当业务执行到需要不同算法实现时,再将职责委托给算法对象来识别并计算。这正是对象的多态性体现。
业务封装实例
策略模式的本意虽然是用来封装算法,但实际业务中我们可以将这个概念泛化为解决某一问题的逻辑。只要目标一致并且可以在适当的时机替换使用,就可以使用策略模式来封装。例如我们现在要实现一个表单验证功能。需求如下:
- 用户名不能为空。
- 密码长度不少于6位。
- 手机号码必须符合格式。
初始实现
按照常规逻辑,很容易写出如下代码:
<html>
<body>
<form action="..." id="registerForm" method="post">
用户名:<input type="text" name="userName"/>
密码:<input type="text" name="password"/>
手机号码:<input type="text" name="phoneNumber"/>
<input type="sumbit" value="Submit"/>
</form>
<script>
let 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;
}
}
</script>
</body>
</html>
虽然功能是实现了,但缺点和之前的算法一样。我们试着用策略模式来重构。
let strategies = {
//用户名验证
isEmpty(value, errorMsg) {
if (value === '') {
return errorMsg;
}
},
// 密码长度验证
minLength(value, length, errorMsg) {
if (value.length < length) {
return errorMsg;
}
},
// 格式验证
isMobile(value, errorMsg) {
if (!/(^1[3|5|8][0-9]{9}$)/.test(value)) {
return errorMsg;
}
}
};
首先我们将验证策略封装为策略对象。然后再编写一个验证逻辑函数去实现功能。
let validata = function() {
let validator = new Validator(); // 创建验证器
validator.add(registerForm.userNmae, 'isEmpty', '用户名不能为空');
validator.add(registerForm.passWord, 'minLength: 6', '密码长度不能少于6位');
validator.add(registerForm.userNmae, 'isMobile', '手机号码格式不正确');
let errorMsg = validator.start(); // 启动校验
return errorMsg;
}
可以看到,我们创建了一个可以启动验证并返回错误消息的函数。在内部,我们创建了一个验证器对象,并将验证策略添加到对象中,然后通过start方法将验证逻辑委托给验证器。下面只要完善验证器的构造函数即可。
let Validator = function() {
this.cache = []; //保存验证规则用于启动时遍历
};
// 添加原型方法
Validator.prototype.add = function(dom, rule, errorMsg) {
let ary = rule.split(':'); //分离带参策略
this.cache.push(function() {
let strategy = ary.shift();
ary.unshift(dom.value) // 表单值添加到参数列表
ary.push(errorMsg); //错误信息添加到参数列表
return strategies[strategy].apply(dom, ary) // 委托验证
});
Validator.prototype.start = function() {
for (let i = 0, validatorFunc; validatorFunc = this.cache[i++];) {
let msg = validatorFunc();
if (msg) {
return msg;
}
}
}
}
重构之后,这一套校验规则就可以复用了。而且当我们需要修改规则时可以非常方便的到策略对象中修改。
总结
策略模式也是一种常用并且有效的设计模式。虽然会在程序中增加一些策略对象,但长远来看增强了程序的拓展性和复用性。当系统越来越复杂时,将会避免许多重复性工作。
-- EOF --