重要的事情说三遍
https://mp.weixin.qq.com/s/75owDiZ1f20OW5EfEL6wAA
https://mp.weixin.qq.com/s/75owDiZ1f20OW5EfEL6wAA
https://mp.weixin.qq.com/s/75owDiZ1f20OW5EfEL6wAA
好了开始~
这个算是我第二次写责任链模式的内容了,上次是给团队做分享的时候,讨论了不少,回去之后我也重新思考了当初没想到的这些问题,重新修改该篇。希望能从此收获更多。
既然针对业务背景,那首先让我们去了解需求吧。相信这个需求场景经常能遇到。
场景描述
登录系统,需要检测是否配置1,配置2,配置…,配置n是否都配置完成了。如果未完成,则需要弹窗,让用户输入配置,配置过程较长,所以可以中途退出配置,直到下次登录或者配置的入口按钮,继续该次的配置。
直观的流程,只需要判断都是哪些个case,弹出对应的弹窗即可。很典型的if else if模型,那针对这无穷无尽的if else,可能我们还可以用switch case去处理,但总归不是这么明确。
如果这时候n和n+1之间需要增加配置n.1,我们需要打开判断的函数,增加一个case,然后把n的弹窗,在完成的时候指向n.1,n.1的完成指向n+1。
如果有帮忙把流程节点很清楚的以代码表现出来,就不再需要关注其他弹窗,直接对n和n+1操作;仅仅把代码按照某种顺序组合起来,需求反馈不清晰的时候,你可能得从第一个函数一直找到所需要的函数为止。
铺垫了这么多,其实是想引入一些比较装逼的设计模式。好在以后面试时候能吹上一波。那我们用什么模式好呢?
翻了一番以前大学时候学过的设计模式书本,我们还是来搜索看看什么是责任链模式吧。
菜鸟教程~
责任链模式(Chain of Responsibility Pattern)为请求创建了一个接收者对象的链。这种模式给予请求的类型,对请求的发送者和接收者进行解耦。这种类型的设计模式属于行为型模式
它的主要意图,主要解决:避免请求发送者与接收者耦合在一起,让多个对象都有可能接收请求,将这些对象连接成一条链,并且沿着这条链传递请求,直到有对象处理它为止
职责链上的处理者负责处理请求,客户只需要将请求发送到职责链上即可,无须关心请求的处理细节和请求的传递,所以职责链将请求的发送者和请求的处理者解耦了。
通过上面凑字数的图片和菜鸟教程复制过来文字。我们大概知道了它是啥了,接下来再抽象一下,把它画成图。
从实现上简单的说,就是每一个具体的处理者,在handleRequest处理请求完毕之后,决定是否要传给下一处理者去处理,过程中处理者只需要关注的是是否处理,不处理就往下传递。
但是实际上,我们应该关注链的组成顺序,因为链的客户只需要有节点处理这个数据的请求,而链的实现者肯定是希望有个接收处理者的优先级,链不应该只有一个能处理的节点。(比如说李四是个前端总监,你说他会希望一个改按钮颜色的需求传到到李四,或者只有他能解决么)
所以这抽象的链条代表了一个处理逻辑,把这部分也抽象成为一个类,我们得到了俩:
材料都准备好了,在前端,JS上我们该如何实现呢?
我用vscode,你呢?哈哈哈哈
chainNode
class ChainNode {
constructor(main, next, options) {
this.main = main
this.next = next
this.options = options
}
start () {
let res = this.main(...arguments)
res && this.next.start(...arguments)
}
setNext (callback) {
this.next = callback
}
}
ResponsibilityChain
class ResponsibilityChain {
constructor() {
this.chainNodes = {} // 责任节点
}
getChainNodes(chainName) { // 获取责任节点
return this.chainNodes[chainName]
}
setChainNodes(name, chainNode) { // 设置责任节点
this.chainNodes[name] = chainNode
}
insertChainNode () {}
chainConstitute(array) { // 链
for (let index = 0; index < array.length; index++) {
let element = this.chainNodes[array[index]]
let next = this.chainNodes[array[index + 1]]
element.next = next
}
}
}
测试一下
function template_fn1 (tmp) {
console.log('配置1', tmp)
if ('have配置1') {
return true
}
//
alert('输入配置1')
return false
}
function template_fn2 (tmp) {
console.log('配置2', tmp)
return true
}
function template_fn3 (tmp) {
console.log('配置3', tmp)
return false
}
function template_fn4 (tmp) {
console.log('配置4', tmp)
return false
}
let responsibilityChain = new ResponsibilityChain()
responsibilityChain.setChainNodes('chainNode_1', new ChainNode(template_fn1))
responsibilityChain.setChainNodes('chainNode_2', new ChainNode(template_fn2))
responsibilityChain.setChainNodes('chainNode_3', new ChainNode(template_fn3))
responsibilityChain.setChainNodes('chainNode_4', new ChainNode(template_fn4))
responsibilityChain.chainConstitute(['chainNode_1', 'chainNode_2', 'chainNode_3', 'chainNode_4'])
let firstNode = responsibilityChain.getChainNodes('chainNode_1')
firstNode.start('handleRequest')
// 配置1 handleRequest
// 配置2 handleRequest
// 配置3 handleRequest
到这里,我们终于可以正式的对这个业务逻辑写一些代码了!太难了
针对每一设置的case,我们都去判断它是否有配置的数据,如果有则返回true,将当期账号的配置数据往下传递;如果没有,则返回false,并且弹窗要求输入配置信息1;
function template_fn1 (tmp) {
console.log('配置1', tmp)
if ('have配置1') {
return true
}
//
alert('输入配置1')
return false
}
一直到链的结束,都没有弹窗的话证明所有的配置信息都已经存在。
关于【配置信息1】输入完毕之后呢。输入完毕,我们应该已经把该数据更新到当前的账户上了。那么,这个更新之后的数据,就是一个新的处理对象,再把它交给这个链来处理。
打个比方,改按钮颜色的需求由张三实习生修改完毕上线,项目的状态(数据)更新,再把项目往责任链输入,就该到李四总监去给客户交付项目了。
再回来需要增加n.1的问题,我们在修改的时候,在构建链的时候,一眼就能看到n和n+1在哪儿,只需要往他们之间添加n.1,更新的处理对象还是往链输入即可。(这个修改的地方还可以有链表结构优化)
这样我们就做到了对修改关闭,对扩展开放的原则。每一个node都是隔离的单一原则。
至此,这就是我所理解的责任链模式的应用。不对之处欢迎指出。受气了,才有动力验证代码,不然学不动啦,手动狗头。