设计模式:我是怎么用策略模式的

164 阅读4分钟

举个小例子

很多公司的年终奖是根据员工的工资基数和年底绩效情况来发放的。例如,绩效为 S 的人年终奖有 4 倍工资,绩效为 A 的人年终奖有 3 倍工资,而绩效为 B 的人年终奖是 2 倍工资。假设财务部要求我们提供一段代码,来方便他们计算员工的年终奖。

最初的代码实现

可以编写一个名为 calculateBonus 的函数来计算奖金。接收两个参数:员工的工资数额和他的绩效考核等级。 代码如下:

var calculateBonus = function( performanceLevel, salary ){
    if ( performanceLevel === 'S' ){
        return salary * 4;
    }

    if ( performanceLevel === 'A' ){
        return salary * 3;
    }

    if ( performanceLevel === 'B' ){
        return salary * 2;
    }
};

calculateBonus( 'B', 20000 ); // 输出:40000
calculateBonus( 'S', 6000 ); // 输出:24000

可以发现,这段代码十分简单,但是存在着显而易见的缺点。下面用策略模式思路重构一下。

使用策略模式重构代码

var strategies = {
    "S": function( salary ){
        return salary * 4;
     },
    "A": function( salary ){
        return salary * 3;
    },
    "B": function( salary ){
        return salary * 2;
    }
};
var calculateBonus = function( level, salary ){
    return strategies[ level ]( salary );
};

console.log( calculateBonus( 'S', 20000 ) ); // 输出:80000
console.log( calculateBonus( 'A', 10000 ) ); // 输出:30000

策略模式

策略模式指的是定义一系列的算法,把它们一个个封装起来。

将不变的部分和变化的部分隔开是每个设计模式的主题,策略模式也不例外,策略模式的目的就是将算法的使用与算法的实现分离开来

我是如何在实际的使用情况

实际场景

有个功能需求,有个上传文件包的功能,有.zip、.tjs、.bunld 等结尾的多种资源。 在后台对于资源上传这件。从技术角度 我们推理一下过程:

    graph TD
    接收文件 --> 解压文件 --> 临时存放 --> 校验文件  --> 放到指定位置 --> 记录日志  --> 删除/备份上传的包

除此之外有一个过程不对,都会进行回滚操作。

使用策略模式

知道业务情况了,按照策略模式思维使用与实现分离开来。将不变的部分和变化的部分隔开。

不变的是什么: 1. 是整个流程 2. 是回滚机制 3. 不同文件存放的地址

变化的是什么: 1. 不同的文件类型 2. 是不同文件的校验机制 3. 是不同文件具体日志内容

  • 首先,将针对不同包,需要自定义的钩子提取出来, 创建 packHandFunc 函数
// 各种包处理函数
const packHandFunc = {
    // 上传.zip包
    Zip: {
       // 读取获取包里面的特定信息,获取包里关键信息,传递给后面
       src: function({...}){
            ....
            return { id:id, outDir: paths.dsh + '/' +userId + '/' + id, inputDir: inputDir}
       },
       // 记录上传日志,返回当前文件包,需要记录的字段
       log: function ({...}){
            .....
            return { id: id, name: bundleJson.name,  updateDate: 'N/A', ... }
       },
       // 根据自身包情况进行格式验证, 返回验证状态
       verify: function({ ... }){
            .....
            return { status:f_ }
        }
        // 成功的回调钩子 按自身需求定义
        successCbFunc:(...) => {...}
    },
    // 上传.map包
    Map:{
        src: function({...}){...},
        log: function({...}){...},
        verify: function({...}){...},
   },
   Tjs: {....},
   Gltf: {....},
   ....
  • 其次,将针对上传不同包,将相同的功能抽离出来
var CommonFunc ={
     // 文件解压
     ZippingInit: async function (...) => {...},
     // 获取包类型
     GetPackTypefunction() ...) => {...},
     // 复制文件包
     Copy: function (...) {...},
     // 检查是否已经存在
     CheckIsExists: function ({outDir, bundleJson, overrideStatus, id}) {
        if(Fse.existsSync(outDir)) {
            if(overrideStatus == 'no'){
                return {success: false, message: '上传失败,该资源已存在,资源ID为: '+id +'!'}
            }
        }
        return {success: true}
     },
     // 记录日志 
     Log: function({...}){...},
     // 调用包格式验证
     verify: fucntion({...}){
         packHandFunc[fileType]["verify"] && packHandFunc[fileType]["verify"]({fileType, userId,  outDir, packageName, timer})
     },
     // 操作结束
     End: function (...) {...},
     // 错误时回滚机制
     RollBack: function (...) {...},
     // 调用自身回调验证
     SuccessCbFunc: function (...) {
         packHandFunc[fileType]["successCbFunc"] && packHandFunc[fileType]["successCbFunc"]({fileType, userId,  outDir, packageName, timer})
     }
}
  • 最后,整体流程串通
// 上传资源入口 暴露出
async function uploadResource (req, dir) {
    ....
     // 1. 下载文件
    let { fields , inputFile } = await CommonFunc.downLoad(req, paths.temp);
    
    // 2. 判断根据包类型
    CommonFunc.GetPackType({...});
    ...
    // 3. 解压文件 
    await CommonFunc.ZippingInit({...});
    ...
    // 4. 校验文件包
    CommonFunc.Verify({...})
    ...
    // 5. 复制临时目录到指定位置
    await CommonFunc.Copy({...})
    ...
    // 日志
    CommonFunc.Log({...}}
    ...
    // 6. 上传成功的回调
    CommonFunc.SuccessCbFunc({...})
    ...
    // 7. 销毁多余的临时文件
    CommonFunc.End(zipSrc, tempPackageSrc)
    ...
    return {success: true...}
}
// ... 主要判断 是否进行回滚

如果不用策略模式的话,就可能需要各种包类型进行if判断 并且不容易维护。

但是将整体流程区分开,分为不变的、变化的和流程入口,这样的代码可以分块调试,更便于维护和扩展,这样后续维护只是需要不断扩充 packHandFunc 函数 就可以了。

看看你生产代码中哪里能使用策略模式,快快改造它吧!ヾ(◍°∇°◍)ノ゙

其他

例子、概念参考 《JS设计模式与实现》