策略模式在实践中的应用

134 阅读1分钟

什么是策略模式

策略模式的定义是:定义一系列的算法,把它们一个个封装起来,并且使他们可以相互替换。 当然了这是官方解释,可能听起来有些晦涩,下面我们直接拿最常用的表单校验来实现策略模式。等完成示例,我们再回头看看这句话,就会更加深刻地理解了。

常见的表单校验

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;
        }
    }

这是非常常见的一段代码,看起来没有任何问题。但是随着开发功能的递增,你会发现这种写法还是有很多缺点的。

  1. 首先所有的校验方法都写在onsubmit函数内,使得这个方法异常庞大,如果后续又有校验规则添加进来则使得这个方法变得臃肿
  2. 规则缺乏弹性,比如如果密码长度改为10了,又或者电话号码校验规则又变了。开发者不得不再次进入函数体内进行更改。
  3. 复用性差,这种表单校验是非常通用的组件,如果在其他地方也想调用类似的校验规则不得不再重写一遍,这样一来重复的代码就遍布整个项目。

用策略模式重构表单校验

接下来我们用策略模式将上面的代码重构一下。

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 就可以了。

现在我们再回过头看看文章最开始对策略模式的定义:定义一系列的算法,把它们一个个封装起来,并且使他们可以相互替换. 这里的一系列算法就是我们的策略对象,封装起来,可复用在项目的其他地方。

策略模式的优缺点

来我们总结一下策略模式的优缺点吧

优点

  1. 利用组合、委托、多态等技术和思想,有效地避免多条件的选择语句
  2. 完美地支持了开放-封闭的规则,把算法都封装在独立的strategies中,使我们更易切换,理解和扩展。
  3. 提高了可复用性,增强了代码的可维护性。

缺点

  1. 增加了策略对象和作为中间层的Validator对象,使代码量增多。
  2. 策略对象里定义了诸多方法,在使用它之前必须对它有较为全面的了解,这无形中就增加了一些工作量,而且违反了最少知识原则。

结语

如果你想要的更好的维护自己的项目,而且在时间允许的情况下,策略模式确实是个不错的选择。关于策略模式实现表单校验还有待优化的地方,下次有时间会继续补充完整的哦。喜欢的就点个赞再走吧!

本文代码内容主要引用自曾探的《Javascript 设计模式与开发实践》