Skip to content

职责链模式(Chain of Responsibility)

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

职责链模式

优点

  • 请求发送者只需要知道链中的第一个节点,从而弱化了发送者和一组接收者之间的强联系。
  • 可以手动指定起始节点,请求并不是一定得从链中的第一个节点开始传递。

缺点

  • 无法保证某个请求一定会被链中的节点处理。(在链尾添加一个保底的节点来处理,类似 case 语句的default
  • 没有实质性作用的节点会带来性能损耗。(避免过长的职责链)

举例:如果早高峰上班,顺利挤上公交,往往因为人太多,实在找不到售票员在哪里,我们的做法往往是把公交卡传递给前一个人,如果够幸运,则第一个就是售票员,否则,继续往上传递,直到到达售票员手里。相信大多数人都遇见过吧,这就是一个职责链模式的现实例子。


场景:现在很多数码新品开售前,都会开一轮预定,刷刷热度。现在有两轮定金缴纳,分别为500元定金和200元定金。在正式开售时,如果支付了500元定金的用户会收到100元优惠券,支付200元定金的用户会收到50元优惠券,没有预定的用户则是普通购买模式,且没有预定的用户库存是有限制的。倘若下单了预定,但没有支付定金,则降级为普通购买模式。

这一需求并不难,无非就是判断订单类型,判断是否已经支付定金,判断库存。

javascript
/**
 * @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→普通

javascript
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) // 手机库存不足

虽然现在已经清晰了很多,但传递的顺序很僵硬,传递请求耦合在业务函数中。如果我现在要再增加一个预订或者删除一个预订,则必须去深入函数内部。也违反了开发-封闭原则。

改进

javascript
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” 已经没有意义了。因此我们可以添加一个方法,手动传递请求给下一个节点。

javascript
// 手动传递请求(在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实现职责链

javascript
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
    }
}

Released under the MIT License.