前端也要学系列:设计模式之策略模式

作前端开发已经好几年了,对设计模式一直没有深刻学习总结过。随着架构相关的工做愈来愈多,愈来愈能感受到设计模式成为了我前进道路上的一个阻碍。因此从今天开始深刻学习和总结经典的设计模式以及面向对象的几大原则。

今天第一天,首先来说策略模式。javascript

什么是策略模式?

GoF四兄弟的经典《设计模式》中,对策略模式的定义以下:前端

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

上边这句话,从字面来看很简单。可是如何在开发过程当中去应用,仅凭一个定义依然是一头雾水。以笔者曾经作过的商户进销存系统为例:java

某超市准备举行促销活动,市场人员通过调查分析制定了一些促销策略:
  1. 购物满100减10
  2. 购物满200减30
  3. 购物满300减50
  4. 。。。

收银软件的界面是这样的(简单示意):程序员

图片描述

咱们应该如何计算实际消费金额?算法

最初的实现是这样的:设计模式

//方便起见,咱们把各个促销策略定义为枚举值: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,有人说我很懒,虽然这样不够优雅但并不影响个人使用,毕竟满减策略再多也多不到哪去。
我只能说,需求永远不是程序员定的。。这时,市场人员说咱们新版程序添加了会员功能,咱们须要支持如下的促销策略:函数

会员促销策略:
  1. 会员充300返60,且首单打9折
  2. 会员充500返100,且首单打8折
  3. 会员充1000返300,且首单打7折
  4. ...

这个时候,若是你还在原先的getActualTotal函数中继续添加if判断,我想若是你的领导review你这段代码,可能会怀疑本身当初怎么把你招进来。。学习

OK,咱们终于下定决心要重构促销策略的代码,咱们能够这么作:spa

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;
    }
}

好了,如今咱们每种策略都有本身独立的空间了,看起来层次分明。可是还有两个问题没有解决:

  1. 随着促销策略的增长,getActualTotal的代码量依然会愈来愈大
  2. 系统缺少弹性,若是须要增长一种策略,那么除了添加一个策略函数,还须要修改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自己并无计算能力,而是将计算全权委托给了策略函数。

由此咱们能够总结出策略模式实现的要点:

  1. 将变化的算法封装成独立的策略函数,并负责具体的计算
  2. 委托函数,该函数接受客户请求,并将请求委托给某一个具体的策略函数

用一张UML图表示以下:
图片描述

怎么样?如今看到上面这张图是否是有了了然于胸的感受?那就赶忙去试一试策略模式吧!


参考书籍:

  1. 《设计模式:可复用面向对象软件的基础》
  2. 《大话设计模式》
  3. 《Javascript设计模式与开发实践》
相关文章
相关标签/搜索