学习职责链模式(Chain of Responsibility Pattern)


文章结构

  • 引用
  • 什么是职责链模式
  • 职责链模式的应用场景
  • 一个简单的例子
  • 在业务代码中的应用
  • 这...这可就尴尬了

引用

一个职责链应用的例子
如何无痛降低 if else 面条代码复杂度

什么是职责链模式

wikipedia: 责任链模式在物件导向程式设计里是一种软件设计模式,它包含了一些命令对象和一系列的处理对象。每一个处理对象决定它能处理哪些命令对象,它也知道如何将它不能处理的命令对象传递给该链中的下一个处理对象。该模式还描述了往该处理链的末尾添加新的处理对象的方法。
定义:使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系,将这些对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它为止。
说人话:通过链式调用降低请求的发送者和各个接收/处理者之间的耦合关系。

职责链模式的应用场景

由于职责链模式可以让一个请求被多个对象接收(???不就是可以让各个函数都有机会执行),所以可以使用该模式来解决代码中大量的if/else嵌套的问题。

一个简单的例子

就是那个随便看哪篇职责链模式都会举到的买手机的例子。买手机的时候可以下订单付定金,之后通过存库来确定能否成功买到手机(MI BOY式营销)。具体字段如下:
orderType: 1/2/0 //订单类型,用于区分用户的订单 1:500定金,2:200定金,0:未支付定金
pay: 0/1 //支付状态,用于判断用户是否支付订单
stock: [number] //库存
当用户发生购买时伪代码就如下了:

if (orderType === 1) {
    if (pay) {
        console.log("It's OK! get ¥500");
    } else {
        if (stock > 0) {
            console.log("It's OK!");
        } else {
            console.log("not OK!");
        }
    }
} else if (orderType === 2) {
    if (pay) {
        console.log("It's OK! get ¥200");
    } else {
        if (stock > 0) {
            console.log("It's OK!");
        } else {
            console.log("not OK!");
        }
    }
} else {
    if (stock > 0) {
        console.log("It's OK!");
    } else {
        console.log("not OK!");
    }
}

这里就应该把普通用户的处理逻辑抽取出来当做单独的函数了吧,那样其实逻辑也不会特别复杂
上面代码看起来就很繁琐,就像是这样
示例图

如果使用职责链模式改写,就是将各个if else的逻辑抽取出来当做一个个的节点,当一个节点不满足执行条件时就调用下一个节点。这里定义:

order500 = function(orderType, pay, stock) {
    if (orderType === 500 && pay) {
        console.log("It's OK! get ¥500");
    } else {
        order200(orderType, pay, stock);
    }
}
order500(500,1,0);  //example

通过函数之间的调用实现if/else判断的拆分。
为了便于之后代码的维护,还需要将函数和函数之间的耦合解开。因此在各个函数不满足if条件时返回固定的值,根据该值来判断是否传递请求。

let order500 = (orderType, pay, stock) => {
    if (orderType === 1 && pay) {
        console.log("It's OK! get ¥500");
    } else {
        return 'nextSuccessor'; //如果失败将请求传递给下一个执行者,无需关注具体是哪一个
    }
};

let order200 = (orderType, pay, stock) => {
    if (orderType === 2 && pay) {
        console.log("It's OK! get ¥200");
    } else {
        return 'nextSuccessor';
    }
};

let orderNormal = (orderType, pay, stock) => {
    if (stock > 0) {
        console.log("It's OK! get ¥0");
    } else {
        console.log("It's not OK, no stock");
    }
};

//构建职责链
class Chain {
    constructor(fn) {
        this.fn = fn;
        this.successor = null;
    }

    setNextSuccessor(successor) { //将successor赋值为下一个节点,并返回该节点
        this.successor = successor;
        return successor;
    }

    passRequest() {
        //进入当前节点的处理逻辑
        let ret = this.fn.apply(this, arguments);
        //若返回值为'nextSuccessor'即不满足执行条件则进入下一个节点的处理逻辑
        if (ret === 'nextSuccessor') {
            return this.successor && this.successor.passRequest.apply(this.successor, arguments);
        }
        return ret;
    }
}

//创建各个节点
let chainOrder500 = new Chain(order500);
let chainOrder200 = new Chain(order200);
let chainOrderNormal = new Chain(orderNormal);
//从第一个节点开始向当前节点添加下一个节点
chainOrder500.setNextSuccessor(chainOrder200).setNextSuccessor(chainOrderNormal);
chainOrder500.passRequest(2, 1, 0);

通过这种方式既实现了职责链模式,让请求能在各个接收者之间链式调用;又能将业务代码和逻辑代码拆分,每个方法只需要关注自己的执行条件和执行逻辑,不满足执行条件返回一个特定值,而不用深究请求传递的顺序问题。
在之后的维护中,如果需要增加判断就可以直接新增一个节点然后在从第一个节点开始向当前节点添加下一个节点这一步中相应位置添加即可。

let order300 = (orderType, pay, stock) => {
    if (orderType === 3 && pay) {
        console.log("It's OK! get ¥300");
    } else {
        return 'nextSuccessor';
    }
};
let chainOrder300 = new Chain(order300);
chainOrder500
    .setNextSuccessor(chainOrder300)
    .setNextSuccessor(chainOrder200)
    .setNextSuccessor(chainOrderNormal);

在业务代码中的应用

翻了好久代码才找到一个if else嵌套多一点的🙉 伪代码如下:

_chooseBtnStyle: function(dataInfo) {
    //特定入口进入
    if (store('isBoughtEntrance')) {
        if (dataInfo.isEffective) {
            //do something
        } else {
            //do something
        }
    }
    //非特定入口且满足isBought和boughtNum
    else if (!dataInfo.isEffective && dataInfo.isBought && dataInfo.boughtNum) {
        //do something
    }
    //非特定入口且满足isOfficial
    else if (dataInfo.isOfficial) {
        //do something
    } else {
        if (!dataInfo.isEffective) {
            //do something
        }
    }
}

将各个if else逻辑拆分,之后的使用和上面买手机的几乎一样。伪代码如下:

let handle1 = (dataInfo) => {
    if (store('isBoughtEntrance')) {
        //特定入口进入的逻辑
    } else {
        return 'nextSuccessor';
    }
}
let handle2 = (dataInfo) => {
    if (!dataInfo.isEffective && dataInfo.isBought && dataInfo.boughtNum) {
        //非特定入口且满足isBought和boughtNum的逻辑
    } else {
        return 'nextSuccessor';
    }
}
//...类似的handleN

let chainHandle1 = new Chain(handle1);
//...类似的创建节点

chainHandle1.setNextSuccessor(chainHandle2).setNextSuccessor(chainHandleN);
//...类似的构建链式关系

chainHandle1.passRequest(dataInfo);

这...这可就尴尬了

职责链模式的优缺点:
优点:

  • 职责链最大的优点就是解耦了请求发送者和N个接收者之间的复杂关系。
  • 职责链可以手动指定起始节点,请求并不是非得从链中的第一个节点开始传递。
    缺点:
  • 不能保证某个请求一定会被链中的节点处理,这种情况可以在链尾增加一个保底的接受者节点来处理这种即将离开链尾的请求。
  • 使程序中多了很多节点对象,可能再一次请求的过程中,大部分的节点并没有起到实质性的作用。他们的作用仅仅是让请求传递下去,从性能当面考虑,要避免过长的职责链带来的性能损耗。

过长的职责链会带来性能损耗,简单逻辑一般又用不到这么复杂的写法 😅
到底什么时候使用,还得具体问题具体分析。