优化实战 第 46 期 - 责任链模式最佳实战

6,301 阅读2分钟

责任链模式

  • 模式定义

    责任链模式 又被叫作 职责链模式 是一种行为设计模式,允许你将请求沿着处理者链进行发送

    chain-of-responsibility.png

    收到请求后, 每个处理者均可对请求进行处理,或将其传递给链上的下个处理者

  • 模式作用

    既可以 控制请求处理的顺序,又可以 在不更改现有代码的情况下在程序中新增处理者

    最重要的是 处理者可以决定不再沿着链传递请求,这可高效地取消所有后续处理步骤

  • 生活示例(公司采购审批流程)

    purchase.png

    金额 5 万元以内,主任一人审批即可;

    金额大于等于 5 万元小于 10 万元,需要主任和副董事长审批;

    金额大于等于 10 万元小于 50 万元,需要主任、副董事长和董事长审批;

    金额大于等于 50 万元,需要主任、副董事长、董事长和董事会审批

设计通用轮子

  • 基础设计

    class Chain {
      constructor() {
        this.middleware = []
      }
      // 添加执行节点,也就是中间件
      use(fn) {
        if (typeof fn !== 'function') {
          throw new TypeError('Middleware must be composed of functions!')
        }
        this.middleware.push(fn)
        // 返回 this 让其可以链式调用
        return this
      }
      // 接收上下文,并触发责任链执行
      transmit(ctx) {
        this.compose(this.middleware)(ctx)
      }
    }
    
  • 核心方法

    function compose(middleware = []) {
      if (!Array.isArray(middleware)) {
        throw new TypeError('Middleware stack must be an array!')
      }
      return (context, next) => {
        let index = -1
        const dispatch = i => {
          // 通过索引来限制 next() 只能被调用一次;如果 next() 被多次调用,则抛出错误
          if (i <= index) return Promise.reject(new Error('next() called multiple times'))
          index = i
          // 陆续取出 middleware 中的每个函数并开始执行
          let fn = middleware[i]
          // 直到最后一个时,next 为 undefined,此时的 fn 不是函数,直接返回 resolve
          if (i === middleware.length) fn = next
          if (!fn) return Promise.resolve()
          // 如果不是最后一个,则也会返回一个 resolve,但 resolve 的内容是取到下一个 middleware 中的函数
          try {
            // 当执行到 next() 函数时,会调用下一个中间件,会重新 return 进入 dispatch 函数内部,继续执行下一个函数
            return Promise.resolve(fn(context, dispatch.bind(null, i + 1)))
          } catch(err) {
            return Promise.reject(err)
          }
        }
        return dispatch(0)
      }
    }
    

    借鉴 koa-compose 的优秀源码,详情可参阅 第35期 - 优秀源码之中间件设计思想 一文

  • 使用轮子

    const chain = new Chain()
    chain
      .use(async (ctx, next) => {
        console.log('1')
        await next()
        console.log('1')
      })
      .use(async (ctx, next) => {
        console.log('2')
        await next()
        console.log('2')
      })
      .use(async (ctx, next) => {
        console.log('3')
      })
      
    chain.transmit({ name: '传入执行链中的上下文' })
    
  • 应用场景

    第 49 期 - 责任链模式在高德地图中的应用

    一起学习,加群交流看 沸点