使用职责链模式来重构你的代码

703 阅读7分钟

在这里我们先想象一个场景,假设我们是一个售卖手机的网站,前期出过500到200的定金活动,现在已经进入正式购买阶段。已经支付过 500 元定金的用户会收到 100 元的商城优惠券,200 元定金的用户可以收到 50 元的优惠券,而之前没有支付定金的用户只能进入普通购买模式,也就是没有优惠券,且在库存有限的情况下不一定保证能买到。 我们可以从服务器拿到以下数据

  • orderType:表示订单类型(定金用户或者普通购买用户),code 的值为 1 的时候是 500 元定金用户,为 2 的时候是 200 元定金用户,为 3 的时候是普通购买用户。
  • pay:表示用户是否已经支付定金,值为 true 或者 false, 虽然用户已经下过 500 元定金的订单,但如果他一直没有支付定金,现在只能降级进入普通购买模式。
  • stock:表示当前用于普通购买的手机库存数量,已经支付过 500 元或者 200 元定金的用户不受此限制。

下面的代码是我们能轻而易举的写出的代码

var order = function( orderType, pay, stock ){
  if ( orderType === 1 ){ // 500 元定金购买模式
    if ( pay === true ){ // 已支付定金
      console.log( '500 元定金预购, 得到 100 优惠券' );
    }else{ // 未支付定金,降级到普通购买模式
      if ( stock > 0 ){ // 用于普通购买的手机还有库存
        console.log( '普通购买, 无优惠券' );
      }else{
        console.log( '手机库存不足' );
      }
    }
  }
  else if ( orderType === 2 ){ // 200 元定金购买模式
    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 优惠券

这段代码可以得到我们想要的结果,但是这段代码的可维护性和可阅读性都很差,下面我们可以使用职责链模式来尝试改进它。

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

问题解决: 职责链上的处理者负责处理请求,客户只需要将请求发送到职责链上即可,无须关心请求的处理细节和请求的传递,所以职责链将请求的发送者和请求的处理者解耦了。

代码如下:

// 500 元订单
var order500 = function (orderType, pay, stock) {
  if (orderType === 1 && pay === true) {
    console.log('500 元定金预购, 得到 100 优惠券')
  } else {
    order200(orderType, pay, stock) // 将请求传递给 200 元订单
  }
}
// 200 元订单
var order200 = function (orderType, pay, stock) {
  if (orderType === 2 && pay === true) {
    console.log('200 元定金预购, 得到 50 优惠券')
  } else {
    orderNormal(orderType, pay, stock) // 将请求传递给普通订单
  }
}
// 普通购买订单
var orderNormal = function (orderType, pay, stock) {
  if (stock > 0) {
    console.log('普通购买, 无优惠券')
  } else {
    console.log('手机库存不足')
  }
}
// 测试结果:
order500(1, true, 500) // 输出:500 元定金预购, 得到 100 优惠券
order500(1, false, 500) // 输出:普通购买, 无优惠券
order500(2, true, 500) // 输出:200 元定金预购, 得到 500 优惠券
order500(3, false, 500) // 输出:普通购买, 无优惠券
order500(3, false, 0) // 输出:手机库存不足

这个函数和前面的order函数执行结果完全一致,但是代码结构已经清晰了很多,我们避免了一些冗余的ifelse,并且将一个体积庞大的函数拆分成逻辑更加清晰的小函数。但是这样拆分明显也有一个问题,请求的传递是强耦合的,即500order和200的order是强耦合的,如果我们需要添加一个400元的订单,那么我又需要去修改函数内部,那么有没有更加灵活的方式呢?答案是有的。

我们可以用一个职责链类Chain和特定的传递请求的字符串'nextSuccessor'来拆分职责链节点

var order500 = function (orderType, pay, stock) {
  if (orderType === 1 && pay === true) {
    console.log('500 元定金预购,得到 100 优惠券')
  } else {
    return 'nextSuccessor' // 我不知道下一个节点是谁,反正把请求往后面传递
  }
}
var order200 = function (orderType, pay, stock) {
  if (orderType === 2 && pay === true) {
    console.log('200 元定金预购,得到 50 优惠券')
  } else {
    return 'nextSuccessor' // 我不知道下一个节点是谁,反正把请求往后面传递
  }
}
var orderNormal = function (orderType, pay, stock) {
  if (stock > 0) {
    console.log('普通购买,无优惠券')
  } else {
    console.log('手机库存不足')
  }
} 

接下来就是使用职责链类Chain

// Chain.prototype.setNextSuccessor 指定在链中的下一个节点
// Chain.prototype.passRequest 传递请求给某个节点
var Chain = function (fn) {
  this.fn = fn
  this.successor = null
}
Chain.prototype.setNextSuccessor = function (successor) {
  return this.successor = successor
}
Chain.prototype.passRequest = function () {
  var ret = this.fn.apply(this, arguments)
  if (ret === 'nextSuccessor') {
    return this.successor && this.successor.passRequest.apply(this.successor, arguments)
  }
  return ret
}

现在我们把 3 个订单函数分别包装成职责链的节点:

var chainOrder500 = new Chain(order500)
var chainOrder200 = new Chain(order200)
var chainOrderNormal = new Chain(orderNormal)

然后指定节点在职责链中的顺序:

chainOrder500.setNextSuccessor(chainOrder200)
chainOrder200.setNextSuccessor(chainOrderNormal)

最后把请求传递给第一个节点:

chainOrder500.passRequest(1, true, 500) // 输出:500 元定金预购,得到 100 优惠券
chainOrder500.passRequest(2, true, 500) // 输出:200 元定金预购,得到 50 优惠券
chainOrder500.passRequest(3, true, 500) // 输出:普通购买,无优惠券
chainOrder500.passRequest(1, false, 0) // 输出:手机库存不足

这样我们可以灵活的增加,修改,删除节点顺序,如果突然来一个需求说需要支持300元的定金, 那我们直接添加一个节点即可

var order300 = function(){
 // 具体实现略
};
chainOrder300= new Chain( order300 );
chainOrder500.setNextSuccessor( chainOrder300);
chainOrder300.setNextSuccessor( chainOrder200); 

使用职责链模式,我们可以只增加一个节点并且调整顺序即可就,并不会像第一个函数一样需要修改订单函数代码,因为在实际场景中,业务逻辑远远比这个复杂,出错率也比这个例子高得多。

异步的职责链

在上文中,我们使用同步返回的'nextSuccess'表示需要传递请求给下一个节点,但是实际开发中肯定会遇到异步场景,这时候我们需要手动的触发一个next方法

Chain.prototype.next= function(){
 return this.successor && this.successor.passRequest.apply( this.successor, arguments );
}; 

如下例子

var fn1 = new Chain(function () {
  console.log(1)
  return 'nextSuccessor'
})
var fn2 = new Chain(function () {
  console.log(2)
  var self = this
  setTimeout(function () {
    self.next()
  }, 1000)
})
var fn3 = new Chain(function () {
  console.log(3)
})
fn1.setNextSuccessor(fn2).setNextSuccessor(fn3)
fn1.passRequest() 

职责链模式优点

  1. 降低了请求发送者和请求接受者的耦合关系,我们不用关心它走了哪一个请求,我们都交给第一个节点处理就行
  2. 增强给对象指派职责的灵活性。通过改变链内的成员或者调动它们的次序,允许动态地新增或者删除责任。
  3. 增加新的请求处理类很方便。

缺点

  1. 不能保证请求一定被接收, 除非我们在链尾处理了这种请求。
  2. 系统性能将受到一定影响,在请求过程中,大部分节点仅仅做了请求传递的作用,并且系统会增加额外的职责链类