javascript策略者模式

转载 : https://www.cnblogs.com/tugenhua0707/p/4722696.html
理解javascript中的策略模式

策略模式的定义是:定义一系列的算法,把它们一个个封装起来,并且使它们可以相互替换。

使用策略模式的优点如下:

优点:1. 策略模式利用组合,委托等技术和思想,有效的避免很多if条件语句。

    2. 策略模式提供了开放-封闭原则,使代码更容易理解和扩展。

    3. 策略模式中的代码可以复用。

一:使用策略模式计算奖金;

下面的demo是我在书上看到的,但是没有关系,我们只是来理解下策略模式的使用而已,我们可以使用策略模式来计算奖金问题;

比如公司的年终奖是根据员工的工资和绩效来考核的,绩效为A的人,年终奖为工资的4倍,绩效为B的人,年终奖为工资的3倍,绩效为C的人,年终奖为工资的2倍;现在我们使用一般的编码方式会如下这样编写代码:

[
复制代码

](javascript:void(0); "复制代码")

<pre style="margin-top: 0px; margin-bottom: 0px; white-space: pre-wrap; word-wrap: break-word; font-family: "Courier New" !important; font-size: 12px !important; max-width: 600px;">var calculateBouns = function(salary,level) { if(level === 'A') { return salary * 4;
} if(level === 'B') { return salary * 3;
} if(level === 'C') { return salary * 2;
}
}; // 调用如下:
console.log(calculateBouns(4000,'A')); // 16000
console.log(calculateBouns(2500,'B')); // 7500</pre>

[
复制代码

](javascript:void(0); "复制代码")

第一个参数为薪资,第二个参数为等级;

代码缺点如下:

  1. calculateBouns 函数包含了很多if-else语句。
  2. calculateBouns 函数缺乏弹性,假如还有D等级的话,那么我们需要在calculateBouns 函数内添加判断等级D的if语句;
  3. 算法复用性差,如果在其他的地方也有类似这样的算法的话,但是规则不一样,我们这些代码不能通用。

2. 使用组合函数重构代码

组合函数是把各种算法封装到一个个的小函数里面,比如等级A的话,封装一个小函数,等级为B的话,也封装一个小函数,以此类推;如下代码:

[
复制代码

](javascript:void(0); "复制代码")

<pre style="margin-top: 0px; margin-bottom: 0px; white-space: pre-wrap; word-wrap: break-word; font-family: "Courier New" !important; font-size: 12px !important; max-width: 600px;">var performanceA = function(salary) { return salary * 4;
}; var performanceB = function(salary) { return salary * 3;
}; var performanceC = function(salary) { return salary * 2 }; var calculateBouns = function(level,salary) { if(level === 'A') { return performanceA(salary);
} if(level === 'B') { return performanceB(salary);
} if(level === 'C') { return performanceC(salary);
}
}; // 调用如下
console.log(calculateBouns('A',4500)); // 18000</pre>

[
复制代码

](javascript:void(0); "复制代码")

代码看起来有点改善,但是还是有如下缺点:

calculateBouns 函数有可能会越来越大,比如增加D等级的时候,而且缺乏弹性。

3. 使用策略模式重构代码

策略模式指的是 定义一系列的算法,把它们一个个封装起来,将不变的部分和变化的部分隔开,实际就是将算法的使用和实现分离出来;算法的使用方式是不变的,都是根据某个算法取得计算后的奖金数,而算法的实现是根据绩效对应不同的绩效规则;

一个基于策略模式的程序至少由2部分组成,第一个部分是一组策略类,策略类封装了具体的算法,并负责具体的计算过程。第二个部分是环境类Context,该Context接收客户端的请求,随后把请求委托给某一个策略类。我们先使用传统面向对象来实现;

如下代码:

[
复制代码

](javascript:void(0); "复制代码")

<pre style="margin-top: 0px; margin-bottom: 0px; white-space: pre-wrap; word-wrap: break-word; font-family: "Courier New" !important; font-size: 12px !important; max-width: 600px;">var performanceA = function(){};
performanceA.prototype.calculate = function(salary) { return salary * 4;
}; var performanceB = function(){};
performanceB.prototype.calculate = function(salary) { return salary * 3;
}; var performanceC = function(){};
performanceC.prototype.calculate = function(salary) { return salary * 2;
}; // 奖金类
var Bouns = function(){ this.salary = null; // 原始工资
this.levelObj = null; // 绩效等级对应的策略对象
};
Bouns.prototype.setSalary = function(salary) { this.salary = salary; // 保存员工的原始工资
};
Bouns.prototype.setlevelObj = function(levelObj){ this.levelObj = levelObj; // 设置员工绩效等级对应的策略对象
}; // 取得奖金数
Bouns.prototype.getBouns = function(){ // 把计算奖金的操作委托给对应的策略对象
return this.levelObj.calculate(this.salary);
}; var bouns = new Bouns();
bouns.setSalary(10000);
bouns.setlevelObj(new performanceA()); // 设置策略对象
console.log(bouns.getBouns()); // 40000
bouns.setlevelObj(new performanceB()); // 设置策略对象
console.log(bouns.getBouns()); // 30000</pre>

[
复制代码

](javascript:void(0); "复制代码")

如上代码使用策略模式重构代码,可以看到代码职责更新分明,代码变得更加清晰。

4. Javascript版本的策略模式

代码如下:

[
复制代码

](javascript:void(0); "复制代码")

<pre style="margin-top: 0px; margin-bottom: 0px; white-space: pre-wrap; word-wrap: break-word; font-family: "Courier New" !important; font-size: 12px !important; max-width: 600px;">var obj = { "A": function(salary) { return salary * 4;
}, "B" : function(salary) { return salary * 3;
}, "C" : function(salary) { return salary * 2;
}
}; var calculateBouns =function(level,salary) { return objlevel;
};
console.log(calculateBouns('A',10000)); // 40000</pre>

[
复制代码

](javascript:void(0); "复制代码")

可以看到代码更加简单明了;

策略模式指的是定义一系列的算法,并且把它们封装起来,但是策略模式不仅仅只封装算法,我们还可以对用来封装一系列的业务规则,只要这些业务规则目标一致,我们就可以使用策略模式来封装它们;

表单效验

比如我们经常来进行表单验证,比如注册登录对话框,我们登录之前要进行验证操作:比如有以下几条逻辑:

  1. 用户名不能为空
  2. 密码长度不能小于6位。
  3. 手机号码必须符合格式。

比如HTML代码如下:

[
复制代码

](javascript:void(0); "复制代码")

<pre style="margin-top: 0px; margin-bottom: 0px; white-space: pre-wrap; word-wrap: break-word; font-family: "Courier New" !important; font-size: 12px !important; max-width: 600px;"><form action = "http://www.baidu.com" id="registerForm" method = "post">
<p>
<label>请输入用户名:</label>
<input type="text" name="userName"/>
</p>
<p>
<label>请输入密码:</label>
<input type="text" name="password"/>
</p>
<p>
<label>请输入手机号码:</label>
<input type="text" name="phoneNumber"/>
</p>
</form></pre>

[
复制代码

](javascript:void(0); "复制代码")

我们正常的编写表单验证代码如下:

[
复制代码

](javascript:void(0); "复制代码")

<pre style="margin-top: 0px; margin-bottom: 0px; white-space: pre-wrap; word-wrap: break-word; font-family: "Courier New" !important; font-size: 12px !important; max-width: 600px;">var registerForm = document.getElementById("registerForm");
registerForm.onsubmit = function(){ if(registerForm.userName.value === '') {
alert('用户名不能为空'); return;
} if(registerForm.password.value.length < 6) {
alert("密码的长度不能小于6位"); return;
} if(!/(^1[3|5|8][0-9]{9}$)/.test(registerForm.phoneNumber.value)) {
alert("手机号码格式不正确"); return;
}
}</pre>

[
复制代码

](javascript:void(0); "复制代码")

但是这样编写代码有如下缺点:

  1. registerForm.onsubmit 函数比较大,代码中包含了很多if语句;
  2. registerForm.onsubmit 函数缺乏弹性,如果增加了一种新的效验规则,或者想把密码的长度效验从6改成8,我们必须改registerForm.onsubmit 函数内部的代码。违反了开放-封闭原则。
  3. 算法的复用性差,如果在程序中增加了另外一个表单,这个表单也需要进行一些类似的效验,那么我们可能又需要复制代码了;

下面我们可以使用策略模式来重构表单效验;

第一步我们先来封装策略对象;如下代码:

[
复制代码

](javascript:void(0); "复制代码")

<pre style="margin-top: 0px; margin-bottom: 0px; white-space: pre-wrap; word-wrap: break-word; font-family: "Courier New" !important; font-size: 12px !important; max-width: 600px;">var strategy = {
isNotEmpty: function(value,errorMsg) { if(value === '') { return errorMsg;
}
}, // 限制最小长度
minLength: function(value,length,errorMsg) { if(value.length < length) { return errorMsg;
}
}, // 手机号码格式
mobileFormat: function(value,errorMsg) { if(!/(^1[3|5|8][0-9]{9}$)/.test(value)) { return errorMsg;
}
}
};</pre>

[
复制代码

](javascript:void(0); "复制代码")

接下来我们准备实现Validator类,Validator类在这里作为Context,负责接收用户的请求并委托给strategy 对象,如下代码:

[
复制代码

](javascript:void(0); "复制代码")

<pre style="margin-top: 0px; margin-bottom: 0px; white-space: pre-wrap; word-wrap: break-word; font-family: "Courier New" !important; font-size: 12px !important; max-width: 600px;">var Validator = function(){ this.cache = []; // 保存效验规则
};
Validator.prototype.add = function(dom,rule,errorMsg) { var str = rule.split(":"); this.cache.push(function(){ // str 返回的是 minLength:6
var strategy = str.shift();
str.unshift(dom.value); // 把input的value添加进参数列表
str.push(errorMsg); // 把errorMsg添加进参数列表
return strategys[strategy].apply(dom,str);
});
};
Validator.prototype.start = function(){ for(var i = 0, validatorFunc; validatorFunc = this.cache[i++]; ) { var msg = validatorFunc(); // 开始效验 并取得效验后的返回信息
if(msg) { return msg;
}
}
};</pre>

[
复制代码

](javascript:void(0); "复制代码")

Validator类在这里作为Context,负责接收用户的请求并委托给strategys对象。上面的代码中,我们先创建一个Validator对象,然后通过validator.add方法往validator对象中添加一些效验规则,validator.add方法接收3个参数,如下代码:

validator.add(registerForm.password,'minLength:6','密码长度不能小于6位');

registerForm.password 为效验的input输入框dom节点;

minLength:6: 是以一个冒号隔开的字符串,冒号前面的minLength代表客户挑选的strategys对象,冒号后面的数字6表示在效验过程中所必须验证的参数,minLength:6的意思是效验 registerForm.password 这个文本输入框的value最小长度为6位;如果字符串中不包含冒号,说明效验过程中不需要额外的效验信息;

第三个参数是当效验未通过时返回的错误信息;

当我们往validator对象里添加完一系列的效验规则之后,会调用validator.start()方法来启动效验。如果validator.start()返回了一个errorMsg字符串作为返回值,说明该次效验没有通过,此时需要registerForm.onsubmit方法返回false来阻止表单提交。下面我们来看看初始化代码如下:

[
复制代码

](javascript:void(0); "复制代码")

<pre style="margin-top: 0px; margin-bottom: 0px; white-space: pre-wrap; word-wrap: break-word; font-family: "Courier New" !important; font-size: 12px !important; max-width: 600px;">var validateFunc = function(){ var validator = new Validator(); // 创建一个Validator对象
/* 添加一些效验规则 */ validator.add(registerForm.userName,'isNotEmpty','用户名不能为空');
validator.add(registerForm.password,'minLength:6','密码长度不能小于6位');
validator.add(registerForm.userName,'mobileFormat','手机号码格式不正确'); var errorMsg = validator.start(); // 获得效验结果
return errorMsg; // 返回效验结果
}; var registerForm = document.getElementById("registerForm");
registerForm.onsubmit = function(){ var errorMsg = validateFunc(); if(errorMsg){
alert(errorMsg); return false;
}
}</pre>

[
复制代码

](javascript:void(0); "复制代码")

下面是所有的代码如下:

[
复制代码

](javascript:void(0); "复制代码")

<pre style="margin-top: 0px; margin-bottom: 0px; white-space: pre-wrap; word-wrap: break-word; font-family: "Courier New" !important; font-size: 12px !important; max-width: 600px;">var strategys = {
isNotEmpty: function(value,errorMsg) { if(value === '') { return errorMsg;
}
}, // 限制最小长度
minLength: function(value,length,errorMsg) { if(value.length < length) { return errorMsg;
}
}, // 手机号码格式
mobileFormat: function(value,errorMsg) { if(!/(^1[3|5|8][0-9]{9}$)/.test(value)) { return errorMsg;
}
}
}; var Validator = function(){ this.cache = []; // 保存效验规则
};
Validator.prototype.add = function(dom,rule,errorMsg) { var str = rule.split(":"); this.cache.push(function(){ // str 返回的是 minLength:6
var strategy = str.shift();
str.unshift(dom.value); // 把input的value添加进参数列表
str.push(errorMsg); // 把errorMsg添加进参数列表
return strategys[strategy].apply(dom,str);
});
};
Validator.prototype.start = function(){ for(var i = 0, validatorFunc; validatorFunc = this.cache[i++]; ) { var msg = validatorFunc(); // 开始效验 并取得效验后的返回信息
if(msg) { return msg;
}
}
}; var validateFunc = function(){ var validator = new Validator(); // 创建一个Validator对象
/* 添加一些效验规则 */ validator.add(registerForm.userName,'isNotEmpty','用户名不能为空');
validator.add(registerForm.password,'minLength:6','密码长度不能小于6位');
validator.add(registerForm.userName,'mobileFormat','手机号码格式不正确'); var errorMsg = validator.start(); // 获得效验结果
return errorMsg; // 返回效验结果
}; var registerForm = document.getElementById("registerForm");
registerForm.onsubmit = function(){ var errorMsg = validateFunc(); if(errorMsg){
alert(errorMsg); return false;
}
};</pre>

[
复制代码

](javascript:void(0); "复制代码")

如上使用策略模式来编写表单验证代码可以看到好处了,我们通过add配置的方式就完成了一个表单的效验;这样的话,那么代码可以当做一个组件来使用,并且可以随时调用,在修改表单验证规则的时候,也非常方便,通过传递参数即可调用;

给某个文本输入框添加多种效验规则

上面的代码我们可以看到,我们只是给输入框只能对应一种效验规则,比如上面的我们只能效验输入框是否为空,

validator.add(registerForm.userName,'isNotEmpty','用户名不能为空');但是如果我们既要效验输入框是否为空,还要效验输入框的长度不要小于10位的话,那么我们期望需要像如下传递参数:

validator.add(registerForm.userName,[{strategy:’isNotEmpty’,errorMsg:’用户名不能为空’},{strategy: 'minLength:6',errorMsg:'用户名长度不能小于6位'}])

我们可以编写代码如下:

[
复制代码

](javascript:void(0); "复制代码")

<pre style="margin-top: 0px; margin-bottom: 0px; white-space: pre-wrap; word-wrap: break-word; font-family: "Courier New" !important; font-size: 12px !important; max-width: 600px;">// 策略对象
var strategys = {
isNotEmpty: function(value,errorMsg) { if(value === '') { return errorMsg;
}
}, // 限制最小长度
minLength: function(value,length,errorMsg) { if(value.length < length) { return errorMsg;
}
}, // 手机号码格式
mobileFormat: function(value,errorMsg) { if(!/(^1[3|5|8][0-9]{9}$)/.test(value)) { return errorMsg;
}
}
}; var Validator = function(){ this.cache = []; // 保存效验规则
};
Validator.prototype.add = function(dom,rules) { var self = this; for(var i = 0, rule; rule = rules[i++]; ){
(function(rule){ var strategyAry = rule.strategy.split(":"); var errorMsg = rule.errorMsg;
self.cache.push(function(){ var strategy = strategyAry.shift();
strategyAry.unshift(dom.value);
strategyAry.push(errorMsg); return strategys[strategy].apply(dom,strategyAry);
});
})(rule);
}
};
Validator.prototype.start = function(){ for(var i = 0, validatorFunc; validatorFunc = this.cache[i++]; ) { var msg = validatorFunc(); // 开始效验 并取得效验后的返回信息
if(msg) { return msg;
}
}
}; // 代码调用
var registerForm = document.getElementById("registerForm"); var validateFunc = function(){ var validator = new Validator(); // 创建一个Validator对象
/* 添加一些效验规则 */ validator.add(registerForm.userName,[
{strategy: 'isNotEmpty',errorMsg:'用户名不能为空'},
{strategy: 'minLength:6',errorMsg:'用户名长度不能小于6位'}
]);
validator.add(registerForm.password,[
{strategy: 'minLength:6',errorMsg:'密码长度不能小于6位'},
]);
validator.add(registerForm.phoneNumber,[
{strategy: 'mobileFormat',errorMsg:'手机号格式不正确'},
]); var errorMsg = validator.start(); // 获得效验结果
return errorMsg; // 返回效验结果
}; // 点击确定提交
registerForm.onsubmit = function(){ var errorMsg = validateFunc(); if(errorMsg){
alert(errorMsg); return false;
}
}</pre>

[
复制代码

](javascript:void(0); "复制代码")

注意:如上代码都是按照书上来做的,都是看到书的代码,最主要我们理解策略模式实现,比如上面的表单验证功能是这样封装的代码,我们平时使用jquery插件表单验证代码原来是这样封装的,为此我们以后也可以使用这种方式来封装表单等学习;

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,029评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,395评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,570评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,535评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,650评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,850评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,006评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,747评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,207评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,536评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,683评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,342评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,964评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,772评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,004评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,401评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,566评论 2 349

推荐阅读更多精彩内容

  • 工厂模式类似于现实生活中的工厂可以产生大量相似的商品,去做同样的事情,实现同样的效果;这时候需要使用工厂模式。简单...
    舟渔行舟阅读 7,727评论 2 17
  • 单例模式 适用场景:可能会在场景中使用到对象,但只有一个实例,加载时并不主动创建,需要时才创建 最常见的单例模式,...
    Obeing阅读 2,058评论 1 10
  • 1. Java基础部分 基础部分的顺序:基本语法,类相关的语法,内部类的语法,继承相关的语法,异常的语法,线程的语...
    子非鱼_t_阅读 31,598评论 18 399
  • 第2章 基本语法 2.1 概述 基本句法和变量 语句 JavaScript程序的执行单位为行(line),也就是一...
    悟名先生阅读 4,132评论 0 13
  • 自从我有记忆开始 你陪伴我的时间就很少 不是因为你不爱我 而是落在你身上的都是重活 白天,你在烈日下劳作 夜晚,你...
    执着英阅读 374评论 0 0