跳转到内容

职责链模式

首发于:2022-04-12

基本概念

责任链模式(Chain of Responsibility)是一种行为设计模式, 允许你将请求沿着处理者链进行发送。 收到请求后, 每个处理者均可对请求进行处理, 或将其传递给链上的下个处理者。

现实生活中的例子

当你想申请假期时,这个申请会由小组领导、部门经理、总经理之中的某一位领导来进行处理,但一开始提出申请的时候,并不知道这个申请之后由哪个领导来处理,也许是部门经理,或者是总经理,你事先不知道这个申请最后到底应该由哪个领导处理。当你提出请假申请时,就会在你的各级领导之间一级一级进行请求传递,直到传递到能处理这一申请的领导。

应用场景

  1. 需要多个对象可以处理同一个请求,具体该请求由哪个对象处理在运行时才确定;
  2. 在不明确指定接收者的情况下,向多个对象中的其中一个提交请求的话,可以使用职责链模式;
  3. 如果想要动态指定处理一个请求的对象集合,可以使用职责链模式;

职责链模式可能在前端代码中见的不多,但是作用域链、原型链、DOM 事件流的事件冒泡,都有职责链模式的影子:

  1. 作用域链: 查找变量时,先从当前上下文的变量对象中查找,如果没有找到,就会从父级执行上下文的变量对象中查找,一直找到全局上下文的变量对象。
  2. 原型链: 当读取实例的属性时,如果找不到,就会查找当前对象关联的原型中的属性,如果还查不到,就去找原型的原型,一直找到最顶层为止。
  3. 事件冒泡: 事件在 DOM 元素上触发后,会从最内层的元素开始发生,一直向外层元素传播,直到全局 document 对象。

优缺点

优点:

  1. 和命令模式类似,由于处理请求的职责节点可能是职责链上的任一节点,所以请求的发送者和接受者是解耦的;
  2. 通过改变链内的节点或调整节点次序,可以动态地修改责任链,符合开闭原则;

缺点:

  1. 并不能保证请求一定会被处理,有可能到最后一个节点还不能处理;
  2. 调试不便,调用层次会比较深,也有可能会导致循环引用;

实现

下面来实现一下申请请假的这个案例,首先不使用职责链模式实现:

js
/* 小组领导处理逻辑 */
const askLeaveGroupLeader = function (duration) {
    if (duration <= 0.5) {
        console.log('小组领导经过一番心理斗争:批准了')
    } else {
        askLeaveDepartmentLeader(duration)
    }
}

/* 部门领导处理逻辑 */
const askLeaveDepartmentLeader = function (duration) {
    if (duration <= 1) {
        console.log('部门领导经过一番心理斗争:批准了')
    } else {
        askLeaveGeneralLeader(duration)
    }
}

/* 总经理处理逻辑 */
const askLeaveGeneralLeader = function (duration) {
    if (duration <= 2) {
        console.log('总经理经过一番心理斗争:批准了')
    } else {
        console.log('总经理:不准请这么长的假')
    }
}

askLeaveGroupLeader(0.5)   // 小组领导经过一番心理斗争:批准了
askLeaveGroupLeader(1)     // 部门领导经过一番心理斗争:批准了
askLeaveGroupLeader(2)     // 总经理经过一番心理斗争:批准了
askLeaveGroupLeader(3)     // 总经理:不准请这么长的假

上面代码的逻辑很清晰,但是问题在于,函数与函数直接是有直接耦合,如果要新增加或减少领导层级等操作,可能就需要改很多代码。

下面使用职责链模式改造:

js
/* 领导基类 */
class Leader {
    constructor() {
        this.nextLeader = null
    }

    setNext(next) {
        this.nextLeader = next
    }
}

/* 小组领导 */
class GroupLeader extends Leader {
    handle(duration) {
        if (duration <= 0.5) {
            console.log('小组领导经过一番心理斗争:批准了')
        } else {
            this.nextLeader.handle(duration)
        }
    }
}

/* 部门领导 */
class DepartmentLeader extends Leader {
    handle(duration) {
        if (duration <= 1) {
            console.log('部门领导经过一番心理斗争:批准了')
        } else {
            this.nextLeader.handle(duration)
        }
    }
}

/* 总经理 */
class GeneralLeader extends Leader {
    handle(duration) {
        if (duration <= 2) {
            console.log('总经理经过一番心理斗争:批准了')
        } else {
            console.log('总经理:不准请这么长的假')
        }
    }
}

const zhangSan = new GroupLeader()
const liSi = new DepartmentLeader()
const wangWu = new GeneralLeader()

zhangSan.setNext(liSi) // 设置小组领导的下一个职责节点为部门领导
liSi.setNext(wangWu) // 设置部门领导的下一个职责节点为总经理

zhangSan.handle(0.5) // 小组领导经过一番心理斗争:批准了
zhangSan.handle(1) // 部门领导经过一番心理斗争:批准了
zhangSan.handle(2) // 总经理经过一番心理斗争:批准了
zhangSan.handle(3) // 总经理:不准请这么长的假

京ICP备18043750号