Libon

责任链模式(ChainOfResponsibility Pattern) - 设计模式

#design-patterns
通过使用责任链模式来优化代码中的大量 if/else 判断。

ToC

假设项目中有这样一个需求:

  1. orderType 参数的值有三种情况
    • === 1 时表示 500 定金
    • === 2 时表示 200 定金
    • === 3 是表示 无优惠券,原价购买
  2. pay 表示是否支付定金
  3. stack 表示还有多少库存

那么根据需求大概可以写出这样的代码:

function order(orderType, pay, stack) {
  if (orderType === 1) { // 500 定金
    if (pay === true) {
      console.log('支付 500 定金成功')
    } else {
      if (stack > 0) {
        console.log('普通订单, 无优惠券')
      } else {
        console.log('库存不足')
      }
    }
  } else if (orderType === 2) { // 200 定金
    if (pay === true) {
      console.log('支付 200 定金成功')
    } else {
      if (stack > 0) {
        console.log('普通订单, 无优惠券')
      } else {
        console.log('库存不足')
      }
    }
  } else if (orderType === 3) {
    if (stack > 0) {
      console.log('普通订单, 无优惠券')
    } else {
      console.log('库存不足')
    }
  }
}

order(1, true, 10)
order(2, false, 10)
order(3, true, 0)

这么做确实可以完成需求, 但是代码并不好看, 同时层级嵌套也比较深, 用了大量的 if/else. 根据这种场景就引申除了这次的主题: 责任链模式.

责任链模式 : 每个函数只处理一种场景的情况(类似单一职责), 如果不符合条件则转交个下一个方法处理, 这种串联的关系就是责任链模式.

根据责任链原则, 可以得出第一版代码:

function order500(orderType, pay, stack) {
  if (orderType === 1 && pay === true) {
    console.log('支付 500 定金成功')
  } else {
    // orderNormal(orderType, pay, stack)
    return 'next'
  }
}

function order200(orderType, pay, stack) {
  if (orderType === 2 && pay === true) {
    console.log('支付 200 定金成功')
  } else {
    // orderNormal(orderType, pay, stack)
    return 'next'
  }
}

function orderNormal(orderType, pay, stack) {
  if (stack > 0) {
    console.log('普通订单, 无优惠券')
  } else {
    console.log('库存不足')
  }
}

function Chain(fn) {
  this.fn = fn
  this.next = null
}

Chain.prototype.set = function (fn) {
  this.next = fn
}

Chain.prototype.run = function () {
  const result = this.fn.apply(this, arguments)

  // 如果得到的返回值是 next, 则转交下一个方法处理
  if (result === 'next') {
    // 如果没有下一个方法, 则会异常
    return this.next && this.next.run.apply(this.next, arguments)
  }
}

// 包装函数, 保证每次调用都会返回一个新的 Chain 对象
var chainOrder500 = new Chain(order500)
var chainOrder200 = new Chain(order200)
var chainOrderNormal = new Chain(orderNormal)

// 设定链条关系
chainOrder500.set(chainOrder200)
chainOrder200.set(chainOrderNormal)

// 运行这个链条
chainOrder500.run(1, true, 10)
chainOrder500.run(2, false, 10)
chainOrder500.run(3, true, 0)

但这种方式还是比较麻烦, 需要对每一个方法进行包装, 可以在 Function 的原型上添加额外的方法, 使用链式调用的方式来优化这个问题.

Function.prototype.after = function (fn) {
  const self = this
  return function () {
    const result = self.apply(this, arguments)

    return result === 'next'
      ? fn.apply(this, arguments)
      : result
  }
}

var order = order500.after(order200).after(orderNormal)

order(1, true, 10)
order(2, false, 10)
order(3, true, 0)

这么做确实解决了问题, 但是日常开发中不应该去给内置构造函数添加方法, 因为这会导致后续接手代码的人很困惑, “为什么这个函数可以直接调用方法, 却没看到定义?”, 所以我们可以自己定义一个构造函数, 然后在原型上添加方法, 最后用实例化后的对象来操作:

function Chain2() {
  this.chain = []
}

Chain2.prototype.add = function (fn) {
  this.chain.push(fn)
  return this
}

Chain2.prototype.run = function () {
  const self = this
  const args = arguments

  for (let i = 0; i < this.chain.length; i++) {
    const result = this.chain[i].apply(this, args)

    if (result === 'next') {
      continue
    } else {
      return result
    }
  }
}

const chain = new Chain2()

chain.add(order500).add(order200).add(orderNormal)

chain.run(1, true, 10)
chain.run(2, false, 10)
chain.run(3, true, 0)

以上.


CD ..