什么是策略模式
策略模式的定义是:定义一系列的算法,把它们一个个封装起来,并且使他们可以相互替换。 当然了这是官方解释,可能听起来有些晦涩,下面我们直接拿最常用的表单校验来实现策略模式。等完成示例,我们再回头看看这句话,就会更加深刻地理解了。
常见的表单校验
const 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[0-9]{10}$/.test(registerForm.phoneNuber.value)) {
alert('电话号码不正确')
return false;
}
}
这是非常常见的一段代码,看起来没有任何问题。但是随着开发功能的递增,你会发现这种写法还是有很多缺点的。
- 首先所有的校验方法都写在onsubmit函数内,使得这个方法异常庞大,如果后续又有校验规则添加进来则使得这个方法变得臃肿
- 规则缺乏弹性,比如如果密码长度改为10了,又或者电话号码校验规则又变了。开发者不得不再次进入函数体内进行更改。
- 复用性差,这种表单校验是非常通用的组件,如果在其他地方也想调用类似的校验规则不得不再重写一遍,这样一来重复的代码就遍布整个项目。
用策略模式重构表单校验
接下来我们用策略模式将上面的代码重构一下。
const strategies = {
isRequired: function(value, errMsg) {
if (value.trim() === '') {
return errMsg || '内容不能为空'
}
},
isMatchLength: function(value, minLength, errMsg) {
if(value.trim().length < minLength) {
return errMsg || `长度不能小于${minLength}`;
}
},
isMobile: function(value, errMsg){
if(!/^1[0-9]{10}$/.test(value)) {
return errMsg || '号码格式不正确';
}
}
}
首先我们把这些校验规则都封装在一个对象里,我们称之为策略对象,这是一个通用的可复用的对象。而且易于拓展。接下来我们还需要实现一个校验对象,我们想象一下这个校验对象应该是要接受用户的一些请求,然后我们通过这个校验对象再委托给策略对象。假设我们现在已经有这个校验对象了。名字暂且叫作Validator。
const registerForm = document.getElementById('registerForm');
const validataFunc = function() {
const validator = new Validator();
//添加校验规则
validator.add(registerForm.username, 'isRequired', '用户名不能为空');
validator.add(registerForm.password, 'isMatchLength:10', '密码长度不少于10位');
validator.add(registerForm.phone, 'isMobile', '手机格式不正确');
const errMsg = validator.start();
return errMsg;
}
registerForm.onsubmit = function() {
const errMsg = validataFunc();
if (errMsg) {
alert(errMsg);
return false;
}
}
现在我们可以看到代码中假设实现了一个校验对象Validator,这个对象拥有两个方法,一个是添加校验规则,一个是开始校验。接下来我们就去编写这个对象。
const Validator = function() {
this.cache = [];
}
Validator.prototype.add = function(dom, rule, errMsg) {
this.cache.push(function(){
let argu = rule.split(':'); //把参数和方法名分开 isMatchLength:10 => ['isMatchLength', 10]
const strategy = argu.shift();//获取策略对象的方法名 isMatchLength
argu.unshift(dom.value);//存入待校验的值 argu = ['123', 'isMatchLegnth']
argu.push(errMsg);//存入提示报错信息 argu = ['123', 'isMatchLength', '密码长度不能少于10位']
return strategies[strategy].apply(dom, argu);
})
}
Validator.prototype.start = function(){
let errMsg = '';
for(let i=0; i<this.cache.length; i++) {
if (this.cache[i] && this.cache[i]()) {
errMsg = this.cache[i]();
break;
}
}
return errMsg;
}
好的,这个校验对象就写好了,它接受来自客户的请求,把请求依次委托给策略对象strategies,然后把结果暂存在缓存中,最后用户发起校验请求,它就返回校验结果。那么现在这个表单校验基本就完成了。经过代码重构之后,我们仅仅通过配置的方式就可以完成一个表单的校验。而且这个策略对象和校验对象可以复用在其他地方了。方便被移植到项目的其他地方。如果想要新增校验规则只需要在策略对象中添加即可。如添加邮箱校验。
isEmail: function(value, errMsg) {
if (!/^[a-z0-9]{1,}\.\@\s+/.test(value)) {
return errMsg
}
}
或者是改变现有的校验规则也是很方便的,比如密码长度限制改为6,只需要将第二个参数改成 isMatchLength:6 就可以了。
现在我们再回过头看看文章最开始对策略模式的定义:定义一系列的算法,把它们一个个封装起来,并且使他们可以相互替换. 这里的一系列算法就是我们的策略对象,封装起来,可复用在项目的其他地方。
策略模式的优缺点
来我们总结一下策略模式的优缺点吧
优点
- 利用组合、委托、多态等技术和思想,有效地避免多条件的选择语句
- 完美地支持了开放-封闭的规则,把算法都封装在独立的strategies中,使我们更易切换,理解和扩展。
- 提高了可复用性,增强了代码的可维护性。
缺点
- 增加了策略对象和作为中间层的Validator对象,使代码量增多。
- 策略对象里定义了诸多方法,在使用它之前必须对它有较为全面的了解,这无形中就增加了一些工作量,而且违反了最少知识原则。
结语
如果你想要的更好的维护自己的项目,而且在时间允许的情况下,策略模式确实是个不错的选择。关于策略模式实现表单校验还有待优化的地方,下次有时间会继续补充完整的哦。喜欢的就点个赞再走吧!
本文代码内容主要引用自曾探的《Javascript 设计模式与开发实践》