职责链模式(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
}
}