Obeta

JavaScript设计模式之策略模式

设计模式源远流长,日常生活中也非常常见,比如一条手机数据线可以给不同的手机充电(只要插头一样),这里手机数据线就是一种策略,即使不同的手机它也可以提供同样的服务.

javascript

设计模式初探

可能还有些人第一次听说或者听说过但是不理解为什么会有这类理论,这里应该介绍一下设计模式的定义~

在面向对象软件设计过程中针对特定问题的简洁而优雅的解决方案.

也就是说设计模式是一种特定问题的最佳实践,只要你遇到了设计模式能解决的问题,那么设计模式一定是最好的解决方案,这个方案是已经存在的,不需要你再去搅脑汁构造出来的.

这些设计模式只是一种解决方案,并不是说需要学习才可以掌握的,如果你经验足够丰富,也许你早就在使用设计模式了,只是你不知道他存在名称而已,而在学习设计模式的过程中你会发现自己平时无意识的已经在使用了书本上早已定义好的设计模式.

原始人积累的打猎经验也是设计模式,对不同的动物有不同的狩猎模式,只是他们没有总结出来并且命名而已.既然我们都知道或者说我们平时都在使用了,为什么还要学习出来呢?

我们想更快的解决平时生活中遇到的问题,你无意识的设计模式只是你丰富的项目经验给你的,如果你没有经历需要解决某个问题,你不会想到这个问题的解决方案,也许你刚刚遇到的问题用设计模式能很轻易的解决,但是你根本不知道有这个设计模式,因此我们需要学习设计模式,能让你更快的找出解决问题的方法.

设计模式非常多,常见的有单例模式,策略模式,代理模式,发布-订阅模式,组合模式等等,下面就说说策略模式.

策略模式概念

定义一系列的算法,把它们一个个封装起来,并且使它们可相互替换.

记得小时候经常帮我爸干一些工匠活,常用的工具有螺丝批,老虎钳,锤子等等.螺丝批就有两种类型刀头的,一种是一字型,另外一种是十字型,当时我就想:这两种螺丝批就刀头不一样,为什么不通过替换不同的刀头来节省成本呢?后来我果然见到了这种组合螺丝批,还不止这两种刀头类型,相当的好用,但是被我爸粗心的搞不见了几次....

我说的这个故事也就是策略模式在生活中的应用,换成组合螺丝批的定义,这里是:

定义一系列的刀头,一个个的封装,使他们可以相互替换.

一个通用的解决方案,我们可以用来解决特定的问题.当然这里是生活中的,我们要说的是代码上的,好像没什么关系一样.

使用策略模式

现在满脑子的都是螺丝批,那么我就用这个写一个例子好了.

把一堆螺丝(包含一字和十字)上到一个机器上,有两种类型的螺丝批,上一个十字螺丝需要 3 分钟,而一字需要两分钟,那么怎么计算一堆螺丝上完需要多久?

// type 螺丝类型
// number 螺丝个数
const screwTime = function(type, number) {
	if (type === 1) {
		return number * 2;
	}
	if (type === 10) {
		return number * 3;
	}
};
screwTime(1, 10); // 20
screwTime(10, 5); // 15

代码非常简单是吧,但是问题来了,后来新增了一种新的类型螺丝,它是六角的,上一个需要 5 分钟,那么代码也要改了,这也很简单,我们稍微增加一个判断就好了.

// type 螺丝类型
// number 螺丝个数
const screwTime = function(type, number) {
	if (type === 1) {
		return number * 2;
	}
	if (type === 10) {
		return number * 3;
	}
	if (type === 6) {
		return number * 5;
	}
};
screwTime(6, 2); // 10

还是很简单,但是有没有想过这个问题,要是后面来了八角的螺丝或者更多类型的螺丝怎么办,难道不断的增加判断语句吗?复用性是不是太差了?

这时候我们需要祭出策略模式来优化我们的代码了.

const strategies = {
	'1': function(number) {
		return number * 2;
	},
	'6': function(number) {
		return number * 5;
	},
	'10': function(number) {
		return number * 3;
	},
};

const screwTime = function(type, number) {
	return strategies[type](number);
};

现在好看很多了,strategies就是不同的刀头盒子,想要哪个直接拿出来替换原来的刀头就可以了,而且如果新增了新的螺丝类型我们不用慌手慌脚,只需在策略strategies中增加对应的解决方法就好了.

策略模式中的表单认证

自打 js 出生以来,用的最多的就是表单验证,这也是 js 被做出来的原因...

而大多数人喜欢使用if来判断表单是否符合要求,但是众多的判断语句让人头疼恶心,所以我们可以尝试使用策略模式来优化我们众多难看的if判断语句了.

我们来一个我们平时写的表单验证代码:

const form = document.getElementById('form');

form.onsubmit = function() {
	if (form.userName.value === '') {
		alert('用户名不能为空!');
		return false;
	}
	if (form.password.value.length < 6) {
		alert('密码不少于6位!');
		return false;
	}
	if (!/(^1[2|3|4|5|6|8][0-9]{9}$)/.test(form.phone.value)) {
		alert('手机号格式错误!');
		return false;
	}
};

你肯定也想得到上面代码的局限性和缺点了.下面我们使用策略模式来优化一下.

// 构造策略类
const strategies = {
	noEmpty: function(value, errMsg) {
		if (value === '') {
			return errMsg;
		}
	},
	minLength: function(value, len, errMsg) {
		if (value.length < len) {
			return errMsg;
		}
	},
	isPhone: function(value, errMsg) {
		if (!/(^1[2|3|4|5|6|8][0-9]{9}$)/.test(value)) {
			return errMsg;
		}
	},
};

然后我们再写一个委托类,接受用户的验证请求并传递给策略类来判断.

const Validator = function(dom, rule, errMsg) {
	const array = rule.split('_'); //获取策略和策略限制的值
	const strategy = array.shift(); //获取用户的策略名
	array.unshift(dom.value);
	array.push(errMsg);
	return strategies[strategy].apply(dom, array); //使用apply来传递参数
};

完成啦!看来也不是很难的,我们来使用一下看看怎么样.

Validator(form.userName, 'noEmpty', '用户名不能为空!');
Validator(form.password, 'minLength_6', '密码不少于6位!');
Validator(form.phone, 'isPhone', '手机号格式错误!');

但是使用起来好像还是有点问题,因为表单只要有一个验证不通过就应该停止的,而上面的好像还有点问题,我们应该针对表单验证这个功能改进一下.

// 改进验证类
const Validator = function(dom, rule, errMsg) {
	this.needCheck = []; //需要执行的函数
};

// 增加检测表单内的数据
Validator.prototype.check = function(dom, strategy, errMsg) {
	const array = rule.split('_'); //获取策略和策略限制的值
	this.needCheck.push(function() {
		const strategy = array.shift(); //获取用户的策略名
		array.unshift(dom.value);
		array.push(errMsg);
		return strategies[strategy].apply(dom, array); //使用apply来传递参数
	});
};

// 开始检测表单
Validator.prototype.start = function() {
	for (const i = 0; i < this.needCheck.length; i++) {
		const msg = this.needCheck[i]();
		if (msg) {
			return msg; //遇到错误直接停止执行并返回错误的信息
		}
	}
};

现在可以使用了,我们来试试:

form.onsubmit = function() {
	const validator = new Validator();
	validator.check(form.userName, 'noEmpty', '用户名不能为空!');
	validator.check(form.password, 'minLength_6', '密码不少于6位!');
	validator.check(form.phone, 'isPhone', '手机号格式错误!');

	const errMsg = validator.start();
	if (errMsg) {
		alert(errMsg);
		return false;
	}
};

基本完成了表单验证这个策略模式了,但是我们还可以改进一下,比如说密码我们需要长度不小于 6 且不能全为数字,这时候我们的策略类可以直接增加一个检测是否全为数字的方法,而Validator类的check方法则需要将第二个策略更改为数组.

更多的我就不说了,都可以自己搞定.

个人随笔记录,内容不保证完全正确,若需要转载,请注明作者和出处.