策略模式的定义
策略模式的定义是,定义一个策略对象,封装一系列算法,算法可以灵活切换使用。
它主要解决在有多种算法相似的情况下,使用 if和else 所带来的复杂和难以维护的问题。
策略模式的例子
有一天,铁匠a打造出了一把宝剑sa,并宣扬整个铁匠圈子里就他这把剑打造得最好,成天得意洋洋地在同行面前炫耀。
事情慢慢地传开,隔壁城的铁匠b一听不乐意了,于是带上自己的得意之作——宝剑sb要去跟铁匠a进行较量。
两把宝剑的数据是这样的:
const sword1 = {
name: 'sa',//宝剑的名字sa
sharpness: 999,//宝剑的锋利度,这数据确实是值得吹嘘
vulnerability: 100,//宝剑的脆弱度,可以说是比较脆弱了
magic: '101',//宝剑的魔法ID
}
const sword2 = {
name: 'sb',//宝剑的名字sb
sharpness: 500,//宝剑的锋利度,也是一把锋利的绝世好剑
vulnerability: 10,//宝剑的脆弱度,这把剑算得上比较坚硬
magic: '102',//宝剑的魔法ID
}
铁匠a抚须一笑,拿出了锋利度比较器,两人把剑放上去,比较器是这样的:
function compareSharpness (s1, s2) {
if (s1.sharpness > s2.sharpness) {
console.log(s1.name + '胜利')
} else {
console.log(s2.name + '胜利')
}
}
比较器启动了
compareSharpness(sword1, sword2)
结果是sa胜利。铁匠b一看就说你这比较器不对劲啊,宝剑怎么能只比较锋利度呢?不行,得加个脆弱度。于是铁匠b掏出一个比较器,一把拍在桌子上,这个比较器长这样:
function compareSharpnessAndDurability (s1, s2) {
const point = 0
for (let key in s1) {
if (key === 'sharpness') {
point += (s1[key] - s2[key])
} else if (key === 'vulnerability') {
point += ((s2[key] - s1[key]) * 10)
//脆弱度是越低越好的,而且脆弱评分的比例要比锋利度高
}
}
if (point > 0) {
console.log(s1.name + '胜利')
} else {
console.log(s2.name + '胜利')
}
}
这个比较器启动了:
compareSharpnessAndDurability(sword1, sword2)
很显然是宝剑sb获胜。但轮到铁匠a不服了,表示这是一把魔剑,能释放大魔法——毁天灭地。铁匠b又回击,别得意,我这也能释放大魔法——神圣湮灭。于是二人又合资买了个新的比较器:
//这个比较器比较先进,记录了一些魔法的评分
const magicList = {
'101': {
name: '毁天灭地',
point: 7000
},
'102': {
name: '神圣湮灭',
point: 6000
}
}
function compareSword (s1, s2) {
const point = 0
for (let key in s1) {
if (key === 'sharpness') {
point += (s1[key] - s2[key])
} else if (key === 'vulnerability') {
point += ((s2[key] - s1[key]) * 10)
//脆弱度是越低越好的,而且脆弱评分的比例要比锋利度高
} else if (key === 'magic') {
point += (magicList[s1[key]].point - magicList[s2[key]].point)
//魔法评分要读取魔法表
}
}
if (point > 0) {
console.log(s1.name + '胜利')
} else {
console.log(s2.name + '胜利')
}
}
这回又到sa胜利了,铁匠b嘴里说着服气认输,却心想不对啊,这魔法就差一点点,怎么评分差那么多,于是悄悄找上比较器的公司。
比较器的公司稍作了研究,说魔法和脆弱度的评分比重都不应该这么算,而且宝剑的数据还有更多,需要改成更复杂的逻辑。于是一个包含数十个if-else几千行的比较器横空出世……后来的开发人员表示无从下手。
那么,如果从用上策略模式呢?
把比较策略都封装在一个对象里,根据对象的属性名来动态选择比较策略。
代码会变成这样:
const strats = {
sharpness: function (v1, v2) {
return v1 - v2
},
vulnerability: function (v1, v2) {
return (v2 - v1) * 10
},
magic: function (v1, v2) {
return magicList[v1].point - magicList[v2].point
},
}
const magicList = {
'101': {
name: '毁天灭地',
point: 7000
},
'102': {
name: '神圣湮灭',
point: 6000
}
}
function compareSword (s1, s2) {
const point = 0
for (let key in s1) {
point += strats[key](s1[key], s2[key])
}
if (point > 0) {
console.log(s1.name + '胜利')
} else {
console.log(s2.name + '胜利')
}
}
以后无论是添加要比较的属性还是修改属性的比较方法,都只需要在策略里面更改就可以了,甚至不同的策略可以拆开在不同的文件里写。
策略模式的优缺点
优点: 1、算法可以自由切换。 2、避免使用多重条件判断。 3、扩展性良好。
缺点: 1、策略函数会增多。
策略模式的使用场景
- 一个系统需要动态地在几种算法中选择一种。
- 一个对象有很多的行为,使用多重的条件选择语句来实现。
基于策略模式的表单验证器
根据对策略模式的理解,我写了一个基于策略模式的表单验证器。
首先定义一个构造函数Validator。
const Validator = function(errFunc){
this.cache=[]
this.errFunc=errFunc
}
其中cache是用来记录现在需要验证的所有字符串的验证函数。
errFunc是用于对错误信息处理的函数。
再定义一个策略对象。
Validator.strategies = {
/**
* @param {string} errorMsg - 错误信息
*/
isNonEmpty: function (errorMsg) {
if(!errorMsg){
errorMsg="输入不能为空"
}
return function (value) {
if (value === "") {
return errorMsg;
}
};
},
/**
* @param {number} length - 最小长度
* @param {string} errorMsg - 错误信息
*/
minLength: function (length, errorMsg) {
if(!errorMsg){
errorMsg="输入内容过少"
}
return function (value) {
if (value.length < length) {
return errorMsg;
}
};
},
/**
* @param {number} length - 最大长度
* @param {string} errorMsg - 错误信息
*/
maxLength: function (length, errorMsg) {
if(!errorMsg){
errorMsg="输入内容不能超出长度限制"
}
return function (value) {
if (value.length > length) {
return errorMsg;
}
};
},
/**
* @param {string} errorMsg - 错误信息
*/
isMobile: function (errorMsg) {
if(!errorMsg){
errorMsg="手机号输入有误"
}
return function (value) {
if (!/(^1[3|5|8][0-9]{9}$)/.test(value)) {
return errorMsg;
}
};
},
};
在策略中定义了开发常用的四种表单验证策略,包括判空、最大最小长度的限制,以及手机号验证。每个策略中最后一个参数都是错误信息。接下来是对验证策略的添加,一个值可以有多个策略进行验证。
/**
*
* @param {string} value - 待验证字符串
* @param {function} strategies - 验证策略
*/
Validator.prototype.add=function(value,strategies){
if(strategies instanceof Array){
for(var i=0;i<strategies;i++){
this.cache.push(strategies[i].bind(null,value))
}
}else{
this.cache.push(strategies.bind(null,value))
}
}
参数strategies需要传入刚才定义的一系列策略中的一个或多个,所有的函数都会被压入到cache中,直到触发check方法
/**
* 验证所有的值的合法性
*/
Validator.prototype.check=function(retain){
for(var i=0;i<this.cache.length;i++){
var msg=this.cache[i]()
if(msg){
this.errFunc(msg)
return false
}
}
if(!retain){
this.cache=[]
}
return true
}
check方法会将cache中所有的函数都执行一遍,如果发现不匹配就返回false
表单验证器的使用
只看代码可能对这个表单验证器的使用有些模糊,下面给出一些表单验证器的使用示例。
var text = "123456"
var validator = new Validator(console.log)//传入这个函数是用于提示错误信息的,可以传入$alert来提示错误信息
validator.add(text,Validator.strategies.minLength(4,'长度未达到要求!'))//加入最小长度为4的验证策略
validator.add(text,Validator.strategies.maxLength(6,'太长了!!!'))//加入最大长度为6
validator.check()
也可以在add方法的第二个参数填入数组形式的策略
//同等效果
validator.add(text,[Validator.strategies.minLength(4),Validator.strategies.maxLength(6)])
//验证策略的错误信息有默认值,可不填
validator.check()
如果验证的字符串没有问题,validator.check()会返回true,如果验证的字符串不符合验证策略的要求,validator.check()会返回false并将错误信息作为第一个参数调用错误提示函数,提示错误信息。