职责链模式
首发于:2022-04-12
基本概念
责任链模式(Chain of Responsibility)是一种行为设计模式, 允许你将请求沿着处理者链进行发送。 收到请求后, 每个处理者均可对请求进行处理, 或将其传递给链上的下个处理者。
现实生活中的例子
当你想申请假期时,这个申请会由小组领导、部门经理、总经理之中的某一位领导来进行处理,但一开始提出申请的时候,并不知道这个申请之后由哪个领导来处理,也许是部门经理,或者是总经理,你事先不知道这个申请最后到底应该由哪个领导处理。当你提出请假申请时,就会在你的各级领导之间一级一级进行请求传递,直到传递到能处理这一申请的领导。
应用场景
- 需要多个对象可以处理同一个请求,具体该请求由哪个对象处理在运行时才确定;
- 在不明确指定接收者的情况下,向多个对象中的其中一个提交请求的话,可以使用职责链模式;
- 如果想要动态指定处理一个请求的对象集合,可以使用职责链模式;
职责链模式可能在前端代码中见的不多,但是作用域链、原型链、DOM 事件流的事件冒泡,都有职责链模式的影子:
- 作用域链: 查找变量时,先从当前上下文的变量对象中查找,如果没有找到,就会从父级执行上下文的变量对象中查找,一直找到全局上下文的变量对象。
- 原型链: 当读取实例的属性时,如果找不到,就会查找当前对象关联的原型中的属性,如果还查不到,就去找原型的原型,一直找到最顶层为止。
- 事件冒泡: 事件在 DOM 元素上触发后,会从最内层的元素开始发生,一直向外层元素传播,直到全局
document
对象。
优缺点
优点:
- 和命令模式类似,由于处理请求的职责节点可能是职责链上的任一节点,所以请求的发送者和接受者是解耦的;
- 通过改变链内的节点或调整节点次序,可以动态地修改责任链,符合开闭原则;
缺点:
- 并不能保证请求一定会被处理,有可能到最后一个节点还不能处理;
- 调试不便,调用层次会比较深,也有可能会导致循环引用;
实现
下面来实现一下申请请假的这个案例,首先不使用职责链模式实现:
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) // 总经理:不准请这么长的假