定义:定义一系列的算法,把它们一个个封装起来,并且使它们可以相互替换。
目的:将算法的使用与算法的实现分离开来
优点:
- 利用组合、委托和多态等技术和思想,有效避免多重条件选择语句
- 对“开放-闭合“原则的支持,算法封装于strategy类中,使得各种“策略”易于切换、理解、扩展
- 策略类中的算法可以复用在其他地方,避免重复的复制粘贴工作
基于策略模式的程序至少由两部分组成:
- 策略类,策略类封装了具体的算法,并负责具体的计算过程。
- 环境类 Context,Context接受客户的请求,随后把请求委托给某一个策略类。要做到这点,说明 Context中要维持对某个策略对象的引用。
以下编写一个名为calculateBonus
的函数来计算每个人的奖金数额,它接收两个参数:员工的工资数额和他的绩效考核等级。
let strategies = {
S: salary => salary * 4,
A: salary => salary * 3,
B: salary => salary * 2
}
let calculateBonus = (level, salary) => {
return strategies[level](salary)
}
console.log(calculateBonus('S', 20000))
console.log(calculateBonus('A', 10000))
运行结果:
80000
30000
多态在策略模式中的体现:正如上面一个简单例子,Context类是没有计算bonus的能力的,而是把这个职责封装在策略对象中,当我们发出计算bonus的动作后,它们会返回不同的计算结果,这就是对象多态性的体现。
在前端工作中的常见应用:
表单校验
在网页开发中,经常离不开对表单对校验,接下来编写一个简单的对表单校验对例子,下面这个例子将不采用策略模式编写
有如下几条校验规则:
- 用户名不能为空
- 密码长度不少于6
- 手机号码须符合格式
let form = document.getElementById('loginForm');
form.onsubmit = () => {
if (form.userName.value === '') {
alert('用户名不能为空');
return false;
}
if (form.password.value.length < 6) {
alert('密码长度不能少于6');
return false;
}
if (!/(^1[3|5|8][0-9]{9}$)/.test(form.phone.value)) {
alert('手机号码不符合格式');
return false;
}
return true;
}
这种编码方式的缺点很明显,比如:
- onsubmit函数太庞大,需要很多if-else
- 缺乏弹性,违反“开放-封闭”原则,要增加多一种校验就要修改onsubmit函数
- 复用性差,其他地方需要用到类似的校验功能又得复制
接下来用策略模式重构此方案:
首先就是要把这些校验逻辑,封装成一个策略对象
let strategies = {
isNonEmpty (val, errMsg) {
if (val === '') {
return errMsg;
}
},
minLength (val, length ,errMsg) {
if (val.length < length) {
return errMsg;
}
},
isMobile (val, errMsg) {
if (!/(^1[3|5|8][0-9]{9}$)/.test(val)) {
return('手机号码不符合格式');
}
}
}
然后就是要实现Validate类(亦即Context),负责接受请求并转交给Strategy对象处理。要写Validate类,就要看我们用它的时候想怎么用,假设我们会这样调用这个Validate类
let validateFunc = () => {
let validator = new validator();
// 添加一些校验规则
validator.add(loginForm.userName, 'isNonEmpty', '用户名不能为空');
validator.add(loginForm.password, 'minLength:6', '密码长度不能少于6');
validator.add(loginForm.phone, 'isMobile', '手机号码格式不对');
let errMsg = validator.start(); // 去校验
return errMsg;
}
loginForm.onsubmit = () => {
let errMsg = validateFunc() // 如果errMsg有值,则证明校验不通过
if (errMsg) {
alert(errMsg);
return false; // 阻止表单提交
}
}
为了能实现这样调用Validator类,需要这样设计,一个add方法,用于添加校验规则,一个start方法,用于校验表单并有可能返回校验失败的信息,以下为Validator类的具体实现
class Validator {
constructor () {
this.cache = []; // 保存校验规则
}
add (dom, rule, errorMsg) {
let args = rule.split(':'); // 把strategy和参数分开
this.cache.push(() => {
let strategy = args.shift(); // 用户定义的校验规则
args.unshift(dom.value); // 把input的value添加进参数列表
args.push(errorMsg); // 把errorMsg添加进参数列表
return strategies[strategy].apply(dom, args);
});
}
start () {
let errorMsg = [];
this.cache.forEach(valid => {
let result = valid();
result && (errorMsg.push(result));
});
if (errorMsg.length) {
return errorMsg;
}
}
}
使用策略模式重构代码后,我们可以简单地通过为某些表单项配置一些校验规则就可以实现校验表单的功能,而且这套校验规则能够在项目其他地方进行复用,还可以进一步封装成插件,优点明显。