⚡【写写代码】记一次把一堆堆的“if”改造成责任链模式

小猫涂卡 69 2022-11-28

起因

前几天在优化Nodejs服务项目的一个Controller代码,忽然感觉里面的代码似乎有点别扭过头了——if-else太多了,下面是它代码的一个大概样子:

async userTagsController(ctx) {
    let findText = ctx.params.userinfo;
    let result = model.getUserInfo(findText); // 从数据库取数据,没找到则返回空对象
    
    if (isObjectNull(result)) {
        if (result.is_blocked == 0) {
            if (result.tags !== "") {
                let outData = result.tags.split("#");
                ctx.succeed(outData);
            }
            else {
                // 未设置标签时的错误返回
            }
        }
        else {
            // 用户被封禁时的错误返回
        }
    }
    else {
        // 未找到用户时的错误返回
    }
}

改造大概

于是,咱决定将一些逻辑抽取出来,然后加点细节,最后就变成了这个样子:

async userTagsController(ctx) {
    let findText = ctx.params.userinfo;
    let result = model.getUserInfo(findText);
    
    let chain_isObjectNull = new Chain(unit_isObjectNull);
    let chain_isBlocked = new Chain(unit_isBlocked);
    let chain_outputTags = new Chain(unit_outputTags);
    
    chain_isObjectNull.after(chain_isBlocked);
    chain_isBlocked.after(chain_outputTags);
    
    let outData = chain_isObjectNull.begin(result);
    ctx.succeed(outData);
} 

这一种看起来是不是简洁很多,而且它所做的事也是一样的。
那个细节,是一种叫 “责任链” 的设计模式,(个人理解)它可以将一个请求处理流程分开成一个个方法(比如条件判断),再将那些方法组合起来形成链条。需要的请求就沿着这条链条传下去,直到某个方法有条件去处理这个请求。
这样处理起来,感觉还很灵活。

摘自菜鸟教程的介绍: 顾名思义,责任链模式(Chain of Responsibility Pattern)为请求创建了一个接收者对象的链。这种模式给予请求的类型,对请求的发送者和接收者进行解耦。这种类型的设计模式属于行为型模式。
在这种模式中,通常每个接收者都包含对另一个接收者的引用。如果一个对象不能处理该请求,那么它会把相同的请求传给下一个接收者,依此类推。

关键代码

先建立一个文件,叫chain.js,里面用来存放责任链实现的关键代码:

class Chain {
    constructor (fn) {
        this.fn = fn;
        this.successor = null;
    }

    after (successor) {
        return this.successor = successor;
    }

    begin () {
        let ret = this.fn.apply (this, arguments);
        if (ret === "after") {
            return this.successor && this.successor.begin.apply (this.successor, arguments);
        }
        return ret;
    }
}

module.exports = Chain;

拆分组合

回到刚刚开头的那一段代码,拆开处理流程吧:

function unit_nullObj (data) {
    if (!isNullObj (data)) {
        // 未找到用户时的错误返回
    }
    else {
        return "after";
    }
}

function unit_isBlocked (data) {
    if (data.is_blocked === 1) {
        // 用户被封禁时的错误返回
    }
    else {
        return "after";
    }
}

function unit_outputTags (data) {
    if (result.tags !== "") {
        let outData = result.tags.split("#");
        ctx.succeed(outData);
    }
    else {
        // 未设置标签时的错误返回
    }
}

然后require刚刚写的chain.js就可以开始组合了,组合完成后大概就长这个样子:

const Chain = require ("@commons/chain");

// function unit_nullObj (data) ...
// function unit_isBlocked (data) ...
// function unit_outputTags (data) ...

async userTagsController(ctx) {
    let findText = ctx.params.userinfo;
    let result = model.getUserInfo(findText);
    
    let chain_isObjectNull = new Chain(unit_isObjectNull);
    let chain_isBlocked = new Chain(unit_isBlocked);
    let chain_outputTags = new Chain(unit_outputTags);
    
    chain_isObjectNull.after(chain_isBlocked); // 将封禁判断置于判断空对象之后
    chain_isBlocked.after(chain_outputTags); // 同理
    
    let outData = chain_isObjectNull.begin(result); // 链条从此开始
    ctx.succeed(outData);
}

测试一下,跟之前的输出基本无异。
突然有一天,这个controller需要判断敏感词,就不用再往一堆堆的if里小心翼翼地加了,直接这样就行:

async userTagsController(ctx) {
    // ...
    let chain_hasSensibility = new Chain(unit_hasSensibility);
    
    // ...
    chain_isBlocked.after(chain_hasSensibility);
    chain_hasSensibility.after(chain_outputTags);
    
    let outData = chain_isObjectNull.begin(result);
    ctx.succeed(outData);
} 

后记

参考博客:
https://blog.csdn.net/wjh1840226173/article/details/115230287