JavaScript设计模式之策略模式

策略模式的概念

策略模式(Strategy)强调的是行为的灵活切换,比如一个类的多个方法有着类似的行为接口,可以将它们抽离出来作为一系列策略类,在运行时灵活对接,变更其算法策略,以适应不同的场景。

例如我们在乔欣、许魏洲主演的电视剧《我不能恋爱的女朋友》中,许魏洲饰演剧中的迟信。电视节目的金牌制作人,永远有PLAN B的职业病先生,在工作过程中由于情况突变而导致预案无法继续实施A计划时,马上更换为B计划,以另一种行为方式达成目标。所以说提前策划非常重要,而随机应变的能力更是不可或缺,系统需要时刻确保灵活性、机动性才能立于不败之地。

一个真实场景——产品促销-策略模式

在双十一大促来临之际,产品经理W小姐提出一个需求:差异化销售

同一个商品,在不同时期下销售价格不同,大致氛围4种:

  1. 当商品处于预售期(尝鲜价)时,商品价格为9折

  1. 当商品处于正季销售时,商品价格为原价

  1. 当商品处于大促期时,商品价格为每满200-50,不满则享受8折

  1. 当商品处于反季期时,商品价格为每满100-50,不满则享受6折


预售期 preSale 正季销售 normalSale 大促期 bigSale 反季期 offSale 新人价newSale


C先生看到该需求的第一反应是这很简单呀,初次的代码实现,编写一个名为askPrice的函数来计算不同时期优惠完的实际价格。askPrice函数需要接受两个参数:代表不同时期的价格标签和原价价格。代码如下:

const askPrice = (tag, originPrice) => {
    
    if(tag === 'pre') {
        // 预售期价
        return originPrice * 0.9
    } else if(tag === 'normal') {
        // 正季销售价
        return originPrice
    } else if(tag === 'big') {
        // 大促期价
        if(originPrice >= 200) {
            let reduceTime = originPrice / 200;
            return originPrice - 50 * reduceTime
        } 
        return originPrice * 0.8
    } else if(tag === 'off') {
        // 反季期价
        if(originPrice >= 100) {
            let reduceTime = originPrice / 100;
            return originPrice - 50 * reduceTime
        } 
        return originPrice * 0.6
    }
}

console.log('预售期价', askPrice('pre', 200)) // 预售期价 180
console.log('正季销售价', askPrice('normal', 200))// 正季销售价 200
console.log('大促期价', askPrice('big', 200))// 大促期价 150
console.log('反季期价', askPrice('off', 200))// 反季期价 100

以上这段代码十分简单,但是存在着显而易见的缺点:

  • askPrice函数比较庞大,包含了很多if-else语句,这些语句需要覆盖所有的逻辑分支。其中一行代码出现bug,那么将影响整个函数的逻辑,很难定位有问题的代码,函数复用性较差,单个功能很难被抽离等等,这是违反了单一功能的原则。

  • askPrice函数缺乏易扩展性,如果增加了一种新品发布newSale,或者由于竞争压力需要调整大促的优惠力度,那我们必须深入askPrice函数的内部实现,这是违反开放-封闭原则的。

与此同时,产品经理W小姐提出了一个新需求,决定增加一个两个需求,(1)新人福利价,第一次购买产品享受5折优惠,(2)在双十一当天满足满减的同时享受9折优惠。以上所有的福利均不累加,只能享受其中一种。随着需求的不断增加,为了提高工作效率及代码质量,减少回归测试,C先生决定重构这段代码。

第一步,实现单一功能的原则,使用组合函数重构代码

const preSale = function(originPrice) {
    return originPrice * 0.9
}

const normalSale = function(originPrice) {
    return originPrice 
}
const bigSale = function(originPrice) {
    if(originPrice >= 200) {
        let reduceTime = originPrice / 200;
        return (originPrice - 50 * reduceTime) * 0.9
    } 
    return originPrice * 0.8
}
const offSale = function(originPrice) {
    if(originPrice >= 100) {
        let reduceTime = originPrice / 100;
        return originPrice - 50 * reduceTime
    } 
    return originPrice * 0.6
}

const newSale = function(originPrice) {
    return originPrice * 0.5
}


const askPrice = function(tag, originPrice) {
    if(tag == 'pre') {
        return preSale(originPrice)
    } else if(tag == 'normal') {
        return normalSale(originPrice)
    } else if(tag == 'big') {
        return bigSale(originPrice)
    } else if(tag == 'off') {
        return offSale(originPrice)
    } else if(tag == 'new') {
        return newSale(originPrice)
    }
}


console.log('预售期价', askPrice('pre', 200)) // 预售期价 180
console.log('正季销售价', askPrice('normal', 200))// 正季销售价 200
console.log('大促期价', askPrice('big', 200))// 大促期价 135
console.log('反季期价', askPrice('off', 200))// 反季期价 100
console.log('新人价', askPrice('new', 200)) // 新人价 100

这段代码C先生做到了一个函数只做一件事,每个函数都有自己明确的分工,符合单一功能的原则。该程序在一定程度上得到了改善,但最终还是没有逃脱掉if-else的魔掌,没有解决根本问题。

第二步,实现开放-封闭的原则,使用策略模式进行重构

const preSale = function(originPrice) {
    return originPrice * 0.9
}

const normalSale = function(originPrice) {
    return originPrice 
}
const bigSale = function(originPrice) {
    if(originPrice >= 200) {
        let reduceTime = originPrice / 200;
        return (originPrice - 50 * reduceTime) * 0.9
    } 
    return originPrice * 0.8
}
const offSale = function(originPrice) {
    if(originPrice >= 100) {
        let reduceTime = originPrice / 100;
        return originPrice - 50 * reduceTime
    } 
    return originPrice * 0.6
}

const newSale = function(originPrice) {
    return originPrice * 0.5
}

const strategies = {
    'pre': preSale,
    'normal':normalSale,
    'big': bigSale,
    'off': offSale,
    'new': newSale
}

const askPrice = function(tag, originPrice) {
   return strategies[tag](originPrice)
}
console.log('预售期价', askPrice('pre', 200)) // 预售期价 180
console.log('正季销售价', askPrice('normal', 200))// 正季销售价 200
console.log('大促期价', askPrice('big', 200))// 大促期价 135
console.log('反季期价', askPrice('off', 200))// 反季期价 100
console.log('新人价', askPrice('new', 200)) // 新人价 100

上面的代码实现了“对扩展开放,对修改封闭”的效果

当产品经理W小姐再提出新需求的时候,C先生可以根据新功能进行小幅度的改造,通过增加一个新函数或者改造某一个函数来满足需求。涉及改动范围较小,代码易读、易维护,且便于测试,只需要测试改动部分,不需要做回归测试。

总结

变化是世界的常态,唯一不变的就是变化本身。策略模式的定义是:定义一系列的算法,把它们一个个封装起来, 并且使它们可以相互替换。

策略模式整体理解难度不大,在面试中几乎没有什么权重,但是却对培养良好的编码习惯和重构意识大有裨益,且利于提高代码质量等。

通过小故事的方式介绍知识点儿来源于《大话设计模式》,通过故事的方式学习知识点儿。

参考书籍:《JavaScript设计模式与开发实践》

《大话设计模式》

《秒懂设计模式》

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值