做前端开发已经好几年了,对设计模式一直没有深入学习总结过。随着架构相关的工作越来越多,越来越能感觉到设计模式成为了我前进道路上的一个阻碍。所以从今天开始深入学习和总结经典的设计模式以及面向对象的几大原则。
今天第一天,首先来讲策略模式
。
GoF四兄弟的经典《设计模式》中,对策略模式的定义如下:
定义一系列的算法,把它们一个个封装起来,并且使它们可互相替换。
上边这句话,从字面来看很简单。但是如何在开发过程中去应用,仅凭一个定义依然是一头雾水。以笔者曾经做过的商户进销存系统为例:
某超市准备举行促销活动,市场人员经过调查分析制定了一些促销策略:
收银软件的界面是这样的(简单示意):
我们应该如何计算实际消费金额?
最初的实现是这样的:
//方便起见,我们把各个促销策略定义为枚举值:0,1,2...
var getActualTotal = function(onSaleType,originTotal){
if(onSaleType===0){
return originTotal-Math.floor(originTotal/100)*10
}
if(onSaleType===1){
return originTotal-Math.floor(originTotal/200)*30
}
if(onSaleType===0){
return originTotal-Math.floor(originTotal/300)*50
}
}
getActualTotal(1,2680); //2208
上面这段代码很简单,而且缺点也很明显。随着满减策略逐渐增多,getActualTotal
函数会越变越大,而且充满了if
判断,稍一疏忽就容易弄错。
OK,有人说我很懒,虽然这样不够优雅但并不影响我的使用,毕竟满减策略再多也多不到哪去。 我只能说,需求永远不是程序员定的。。 这时,市场人员说我们新版程序添加了会员功能,我们需要支持以下的促销策略:
会员促销策略:
这个时候,如果你还在原先的getActualTotal
函数中继续添加if
判断,我想如果你的领导review你这段代码,可能会怀疑自己当初怎么把你招进来的。
我们终于下定决心要重构促销策略的代码,我们可以这么做:
var vipPolicy_0=function(originTotal){
return originTotal-Math.floor(originTotal/100)*10
}
var vipPolicy_1=function(originTotal){
return originTotal-Math.floor(originTotal/200)*30
}
...
//会员充1000返300
var vipPolicy_10=function(account,originTotal){
if(account===0){
account+=1300;
return originTotal*0.9
}else{
account+=1300;
return originTotal;
}
return originTotal-Math.floor(originTotal/200)*30
}
...
var vipPolicy_n=function(){
...
}
var getActualTotal=function(onSaleType,originTotal,account){
switch(onSaleType){
case 0:
return vipPolicy_0(originTotal);
case 1:
return vipPolicy_0(originTotal);
...
case n:
return ...
default:
return originTotal;
}
}
好了,现在我们每种策略都有自己独立的空间了,看起来井井有条。 但是还有两个问题没有解决:
getActualTotal
的代码量依然会越来越大switch...case..
语句让我们再来回顾一下策略模式的定义:
定义一系列的算法,把它们一个个封装起来,并且使它们可互相替换
在我们的例子中,每种促销策略的实现方式是不一样的,但我们最终的目的都是为了求得实际金额。 策略模式可以把我们对促销策略的算法一个个封装起来,并且使它们可互相替换而不影响我们对实际金额的求值,这正好是我们所需要的。
下面我们用策略模式来重构上面的代码:
var policies={
"Type_0":function(originTotal){
return originTotal-Math.floor(originTotal/100)*10
},
"Type_1":function(originTotal){
return originTotal-Math.floor(originTotal/200)*30
},
...
"Type_n":function(originTotal){
...
}
}
var getActualTotal=function(onSaleType,originTotal,account){
return policies["Type_"+onSaleType](originTotal,account)
}
//执行
getActualTotal(0,2680.00);//2208
分析上面的代码我们发现,不管促销策略如何增加,getActualTotal
函数完全不需要再变化了。我们要做的,就是增加新策略的函数而已。
通过策略模式的代码,我们消除了让人反胃的大片条件分支语句,getActualTotal
本身并没有计算能力,而是将计算全权委托给了策略函数。
由此我们可以总结出策略模式实现的要点:
用一张UML图标识如下:
怎么样?现在看到上面这张图是不是有了了然于胸的感觉?那就赶紧去试一试策略模式吧!
参考书籍: