职责链模式(Chain of Responsibility)
职责链模式的定义:使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系,将这些对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它为止。其中这些对象成为链中的节点。

优点:
- 请求发送者只需要知道链中的第一个节点,从而弱化了发送者和一组接收者之间的强联系。
- 可以手动指定起始节点,请求并不是一定得从链中的第一个节点开始传递。
缺点:
- 无法保证某个请求一定会被链中的节点处理。(在链尾添加一个保底的节点来处理,类似
case
语句的default
) - 没有实质性作用的节点会带来性能损耗。(避免过长的职责链)
举例:如果早高峰上班,顺利挤上公交,往往因为人太多,实在找不到售票员在哪里,我们的做法往往是把公交卡传递给前一个人,如果够幸运,则第一个就是售票员,否则,继续往上传递,直到到达售票员手里。相信大多数人都遇见过吧,这就是一个职责链模式的现实例子。
场景:现在很多数码新品开售前,都会开一轮预定,刷刷热度。现在有两轮定金缴纳,分别为500元定金和200元定金。在正式开售时,如果支付了500元定金的用户会收到100元优惠券,支付200元定金的用户会收到50元优惠券,没有预定的用户则是普通购买模式,且没有预定的用户库存是有限制的。倘若下单了预定,但没有支付定金,则降级为普通购买模式。
这一需求并不难,无非就是判断订单类型,判断是否已经支付定金,判断库存。
/** * @description: * @param {Number} orderType 订单类型 1 500元用户订单 2 200元用户订单 3 普通用户订单 * @param {Boolean} pay 是否已经支付定金 * @param {Number} stock 库存 * @return {type} */const order = function (orderType, pay, stock) { if (orderType === 1) { if (pay === true) { console.log('500元定金预约,赠送100元优惠券'); } else { if (stock > 0) { console.log('普通购买,无优惠券'); } else { console.log('手机库存不足'); } } } else if (orderType === 2) { if (pay === true) { console.log('200元定金预约,赠送50元优惠券'); } else { if (stock > 0) { console.log('普通购买,无优惠券'); } else { console.log('手机库存不足'); } } } else if (orderType === 3) { if (stock > 0) { console.log('普通购买,无优惠券'); } else { console.log('手机库存不足'); } }}
order(1, true, 500)// 输出:500元定金预约,赠送100元优惠券
上面的代码给人的感觉就是太多 if else
了,如果继续加条件则继续嵌套下去,无论阅读和日后维护都是噩梦般存在。
上例的请求模式:

初步拆分
分析:利用职责链模式,把订单类型拆分成3个函数,如果不符合该类型则传递给下一个。500→200→普通
const order500 = function (orderType, pay, stock) { if (orderType === 1 && pay === true) { console.log('500元定金预定,赠送100元优惠券'); } else { order200(orderType, pay, stock) }}
const order200 = function (orderType, pay, stock) { if (orderType === 2 && pay === true) { console.log('200元定金预定,赠送50元优惠券'); } else { orderNormal(orderType, pay, stock) }}
const orderNormal = function (orderType, pay, stock) { if (stock > 0) { console.log('普通购买,无优惠卷'); } else { console.log('手机库存不足'); }}
order500(1, true, 500) // 500元定金预定,赠送100元优惠券order500(2, false, 0) // 手机库存不足
虽然现在已经清晰了很多,但传递的顺序很僵硬,传递请求耦合在业务函数中。如果我现在要再增加一个预订或者删除一个预订,则必须去深入函数内部。也违反了开发-封闭原则。
改进
const order500 = function (orderType, pay, stock) { if (orderType === 1 && pay === true) { console.log('500元定金预定,赠送100元优惠券'); } else { return 'nextSuccessor' }}
const order200 = function (orderType, pay, stock) { if (orderType === 2 && pay === true) { console.log('200元定金预定,赠送50元优惠券'); } else { return 'nextSuccessor' }}
const orderNormal = function (orderType, pay, stock) { if (stock > 0) { console.log('普通购买,无优惠券'); } else { console.log('手机库存不足'); }}
const Chain = function (fn) { this.fn = fn this.successor = null // 下个节点}
// 设置链中的下一个节点Chain.prototype.setNextSuccessor = function (successor) { return this.successor = successor}
// 传递请求给某个节点Chain.prototype.passRequest = function () { const ret = this.fn.apply(this, arguments) if (ret === 'nextSuccessor') { return this.successor && this.successor.passRequest.apply(this.successor, arguments) }
return ret}
// 将函数包装成职责链的节点const chainOrder500 = new Chain(order500)const chainOrder200 = new Chain(order200)const chainOrderNormal = new Chain(orderNormal)
// 指定节点在职责链中的顺序chainOrder500.setNextSuccessor(chainOrder200)chainOrder200.setNextSuccessor(chainOrderNormal)
chainOrder500.passRequest(1, true, 500) // 500元定金预定,赠送100元优惠券chainOrder500.passRequest(2, false, 0) // 手机库存不足
这样以后,如果需要增加、删除或修改节点的顺序都可以轻松实现,增加就 new
多一个节点添加到链上,通过 setNextSuccessor
可以随意修改链中节点顺序。
异步职责链
在上面的例子中,每个节点函数都是同步返回一个 “nextSuccessor”,但在实际项目中往往还会伴随则异步的问题,可能下一个节点的执行,需要上一个节点的异步请求返回的结果才能决定,这时让函数同步返回 “nextSuccessor” 已经没有意义了。因此我们可以添加一个方法,手动传递请求给下一个节点。
// 手动传递请求(在Chain类中增加)Chain.prototype.next = function () { return this.successor && this.successor.passRequest.apply(this.successor, arguments)}
// 如:const fn = new Chain(function () { console.log(1); const _self = this setTimeout(function () { _self.next() }, 1000)})
// 其余使用方法与上例一致
用AOP实现职责链
Function.prototype.after = function (fn) { const self = this return function () { const ret = self.apply(this, arguments) if (ret === 'nextSuccessor') { return fn.apply(this, arguments) }
return ret }}