js设计模式——策略模式
策略模式:
定义一系列算法,把它们一个个封装起来,并且使它们可以相互替代。
这段话读了之后可能还不是很理解策略模式的意思。可能需要一些实际例子来让自己对策略模式更加了解。
计算奖金的例子
这是计算奖金的例子,要求通过不同的绩效考核等级(A,B,C)和员工的工资来计算奖金:
var calculateBonus = function( performanceLevel, salary ){
if ( performanceLevel === 'A' ){
return salary * 3;
}
if ( performanceLevel === 'B' ){
return salary * 2;
}
if ( performanceLevel === 'C' ){
return salary * 1;
}
};
calculateBonus( 'B', 20000 ); // 输出:40000
calculateBonus( 'C', 6000 ); // 输出:6000
这段代码很简单,但是缺点也很明显:
- 代码冗余。包含了大量if-else语句。
- 缺乏弹性。如果要新增一种绩效,比如S等级,那无疑要深入到
calculateBonus
函数内部去修改代码,这违背了“开放-封闭原则”
开放-封闭原则:软件实体是可以扩展的,而不可修改的。即对扩展是开放的,对修改是封闭的
- 代码复用性差。别的地方要使用这个功能,只能粘贴复制这段代码。
使用策略模式来重构
我们使用策略模式来重构这段代码。
实现策略模式的方法:将代码抽离出不变的部分和变化的部分,并相互组合。
策略模式的目的就是将算法的使用和算法的实现分离开来。
一个策略模式程序至少由两部分组成,第一个部分是策略类(strategy),另一个是环境类Context。环境类接收客户请求,然后将请求委托一个策略类。
- 不变的部分,算法的使用,环境类Context,一个
- 变化的部分,算法的实现,策略类Strategy,多个
核心代码
var strategy={
"A":function(salary){
return salary*3;
},
"B":function(salary){
return salary*2;
},
"C":function(salary){
return salary*1;
}
}
function calculateBonus(level,salary){
return strategy[level](salary);
}
策略模式:定义一些列算法(策略类strategy中有A,B,C这些策略),把它们一个个封装起来(A,B,C策略的实现都封装在了函数中),并且他们可以相互替换(在calculateBonus
函数中,A,B,C三个策略的地位相等,可以替换)
现在看策略模式是不是解决了前面的简单实现代码的问题呢?
- 去除了if-else语句,
calculateBonus
函数中只有很清爽的一句代码,且良好的命名使这段代码的意思一目了然,可读性也很好。 - 可扩展。如果我们增加了一个S等级,那么无需深入
calculateBonus
函数,只需在strategy
里面添加上S等级的算法实现即可。这符合开放-封闭原则。 - 复用性。这个例子比较简单,这里可能体现不出来复用性。我们下个表单校验的例子来看。
运行代码
<!DOCTYPE html>
<html>
<head>
<title>策略模式</title>
</head>
<body>
原始薪资:
<input type="text" name="salary" id="myInput">
绩效:
<select id="mySelect">
<option value="A">A</option>
<option value="B">B</option>
<option value="C">C</option>
</select>
<button id="myBtn">计算奖金</button>
<div id="myDiv"></div>
</body>
<script type="text/javascript">
var strategy={
"A":function(salary){
return salary*3;
},
"B":function(salary){
return salary*2;
},
"C":function(salary){
return salary*1;
}
}
function calculateBonus(level,salary){
return strategy[level](salary);
}
myBtn.onclick=function(){
var myInputValue=parseInt(document.getElementById('myInput').value);
var mySelect=document.getElementById('mySelect');
var myBtn=document.getElementById('myBtn');
var myDiv=document.getElementById('myDiv');
var mySelectIndex=parseInt(mySelect.selectedIndex);
var mySelectValue=mySelect.options[mySelectIndex].value;
myDiv.innerHTML=calculateBonus(mySelectValue,myInputValue);
}
</script>
</html>
表单校验
表单校验是很常用的,而且有时候也会比较繁琐。这里就直接给出代码。可粘贴复制后运行,测试一下。
<!DOCTYPE html>
<html>
<head>
<title>策略模式--表单验证</title>
</head>
<body>
<form id="myForm" method="post">
用户名:<input type="text" name="myName">
密码:<input type="password" name="myPassword">
电话号码:<input type="text" name="myPhoto">
<button>提交</button>
</form>
</body>
<script type="text/javascript">
var myForm=document.getElementById('myForm');
//策略,将变化的元素集中起来
var strategies={
isNoEmpty:function(value,errorMsg){
if(value==='')
{
return errorMsg;
}
},
minLength:function(value,length,errorMsg){
if(value.length<parseInt(length)){
return errorMsg;
}
},
reg:function(value,reg,errorMsg){
var regExp = eval(reg)
if(!regExp.test(value)){
return errorMsg;
}
}
}
var Vaildator=function(){
this.cache=[];//校验规则列表
}
//添加校验规则
Vaildator.prototype.add=function(dom,rules,errorMsg){
var ary=rules.split(":");//rules='minLength:6'=>ary=["minLength","6"],ary就是策略算法中的参数
this.cache.push(function(){
var strategy=ary.shift();//获取策略,比如minLength,此时ary=["6"]
ary.unshift(dom.value);//加入value值,此时ary=["xxxx","6"]
ary.push(errorMsg);//加入错误提示信息
return strategies[strategy].apply(dom,ary);
})
}
//启动校验
Vaildator.prototype.start=function(){
for(let i=0;i<this.cache.length;i++){
let vaildatorFun=this.cache[i];
var msg=vaildatorFun();
if(msg){
return msg;
}
}
}
function vaildatorFun(){
var vaildator=new Vaildator();
vaildator.add(myForm.myName,'isNoEmpty',"用户名不能为空");
vaildator.add(myForm.myPassword,'minLength:10',"密码长度要大于10位");
vaildator.add(myForm.myPhoto,'reg:/^((13[0-9])|(14[5|7])|(15([0-3]|[5-9]))|(18[0,5-9]))\\d{8}$/',"手机号格式不正确");
var errorMsg=vaildator.start();
return errorMsg;
}
myForm.onsubmit=function(){
var errorMsg=vaildatorFun();
if(errorMsg){
alert(errorMsg);
}else{
alert("验证通过")
}
return false
}
</script>
</html>
这段代码中策略放在strategies
中,环境是Vaildator
,我们只需进行vaildator.add()
就可以添加验证规则了。
我们可以将这种表单校验代码封装起来,暴露必须的接口,就可以做成插件或者js文件。复用性也就体现出来了。
表单多种校验规则
这里再进一步添加多种校验规则。
<!DOCTYPE html>
<html>
<head>
<title>策略模式--表单验证</title>
</head>
<body>
<form id="myForm" method="post">
用户名:<input type="text" name="myName">
密码:<input type="password" name="myPassword">
电话号码:<input type="text" name="myPhoto">
<button>提交</button>
</form>
</body>
<script type="text/javascript">
var myForm=document.getElementById('myForm');
//策略,将变化的元素集中起来
var strategies={
isNoEmpty:function(value,errorMsg){
if(value==='')
{
return errorMsg;
}
},
minLength:function(value,length,errorMsg){
if(value.length<parseInt(length)){
return errorMsg;
}
},
reg:function(value,reg,errorMsg){
var regExp = eval(reg)
if(!regExp.test(value)){
return errorMsg;
}
}
}
var Vaildator=function(){
this.cache=[];//校验规则列表
}
Vaildator.prototype.add=function(dom,rules,errorMsg){
var that=this;
for (var i = 0,rule; i <rules.length,rule=rules[i]; i++) {
//闭包,this指向全局
(function(rule){
var strategyAry=rule.strategy.split(':');
var errorMsg=rule.errorMsg;
that.cache.push(function(){
var strategy=strategyAry.shift();
strategyAry.unshift(dom.value);
strategyAry.push(errorMsg);
return strategies[strategy].apply(dom,strategyAry);
})
})(rule)
}
}
Vaildator.prototype.start=function(){
for(let i=0;i<this.cache.length;i++){
let vaildatorFun=this.cache[i];
var msg=vaildatorFun();
if(msg){
return msg;
}
}
}
function vaildatorFun(){
var vaildator=new Vaildator();
vaildator.add(myForm.myName,[{
strategy:"isNoEmpty",
errorMsg:"用户名不能为空"
},{
strategy:"minLength:3",
errorMsg:"用户名长度不小于3"
}]);
vaildator.add(myForm.myPassword,[{
strategy:'minLength:10',
errorMsg:"密码长度要大于10位"
}]);
vaildator.add(myForm.myPhoto,[{
strategy:'reg:/^((13[0-9])|(14[5|7])|(15([0-3]|[5-9]))|(18[0,5-9]))\\d{8}$/',
errorMsg:"手机号格式不正确"
}]);
var errorMsg=vaildator.start();
return errorMsg;
}
myForm.onsubmit=function(){
var errorMsg=vaildatorFun();
if(errorMsg){
alert(errorMsg);
}else{
alert("验证通过")
}
return false
}
</script>
</html>
参考:javascript设计模式与开发
记录下来用于自己回顾