共计 6583 个字符,预计需要花费 17 分钟才能阅读完成。
目标
在开始学习责任链之前,先看一下在开发中常见的问题。上面是前端用来解决 API 错误码的代码:
const httpErrorHandler = (error) => {
const errorStatus = error.response.status;
if (errorStatus === 400) {console.log('你是不是提交了什么奇怪的货色?');
}
if (errorStatus === 401) {console.log('须要先登陆!');
}
if (errorStatus === 403) {console.log('是不是想偷摸干坏事?');
}
if (errorStatus === 404) {console.log('这里什么也没有...');
}
};
当然理论我的项目中不可能只有一行 console
,这是为了阐明原理的简化版。
代码中的 httpErrorHandler
会接管 API 的响应谬误,并对谬误的状态码做不同的解决,所以代码中须要很多 if
(或者 switch
)判断以后须要要执行什么,当你要对新的谬误增加解决代码时,就必须要到 httpErrorHandler
中批改代码。
尽管免不了要常常批改代码,然而这样做可能会导致几个问题,上面依据 SOLID 的 繁多职责(Single responsibility)和凋谢关闭(open/close)这两个准则来阐明:
繁多职责(Single responsibility)
简略的说,繁多职责就是只做一件事件。而后面的 httpErrorHandler
办法以应用的角度来说,是把谬误对象交给它,让它依照错误码做对应的解决。看上去如同是在做“错误处理”这个繁多的事件,然而从实现的角度上来说,它把不同谬误的解决逻辑全副写在了 httpErrorHandler
中,这就会导致可能在只想要批改对错误码为 400 的逻辑时,然而不得不浏览一大堆不相干的代码。
凋谢关闭准则(open/close)
凋谢关闭准则是指对曾经写好的外围逻辑就不要再去改变,但同时要可能因需要的减少而裁减本来的性能,也就是凋谢裁减性能,同时关闭批改本来正确的逻辑。再回过头来看 httpErrorHandler
,如果须要减少一个对错误码 405 的解决逻辑(要裁减新性能),那就须要批改 httpErrorHandler
中的代码(批改本来正确的逻辑),这也很容易造成原来正确执行的代码出错。
既然 httpErrorHandler
漏洞这么多,那该怎么办?
解决问题
拆散逻辑
先让 httpErrorHandler
合乎繁多准则。首先把每个谬误的解决逻辑别离拆成办法:
const response400 = () => {console.log('你是不是提交了什么奇怪的货色?');
};
const response401 = () => {console.log('须要先登陆!');
};
const response403 = () => {console.log('是不是想偷摸干坏事?');
};
const response404 = () => {console.log('这里什么也没有...');
};
const httpErrorHandler = (error) => {
const errorStatus = error.response.status;
if (errorStatus === 400) {response400();
}
if (errorStatus === 401) {response401();
}
if (errorStatus === 403) {response403();
}
if (errorStatus === 404) {response404();
}
};
尽管只是把每个区块的逻辑拆成办法,但这曾经能够让咱们在批改某个状态码的错误处理时,不必再到 httpErrorHandler
中浏览大量的代码了。
仅仅是拆散逻辑这个操作同时也让 httpErrorHandler
合乎了凋谢关闭准则,因为在把错误处理的逻辑各自拆分为办法的时候,就等于对那些曾经实现的代码进行了封装,这时当须要再为 httpErrorHandler
减少对 405 的错误处理逻辑时,就不会影响到其余的错误处理逻辑的办法(关闭批改),而是另行创立一个新的 response405
办法,并在 httpErrorHandler
中加上新的条件判断就行了(凋谢裁减新性能)。
当初的 httpErrorHandler
其实是策略模式(strategy pattern),httpErrorHandler
用了对立的接口(办法)来解决各种不同的谬误状态,在本文的最初会再次解释策略模式和责任链之间的区别。
责任链模式(Chain of Responsibility Pattern)
责任链的实现原理很简略,就是把所有办法串起来一个一个执行,并且每个办法都只做本人要做的事就行了,例如 response400
只在遇到状态码为 400 的时候执行,而 response401
只解决 401 的谬误,其余办法也都只在本人该解决的时候执行。每个人各司其职,就是责任链。
接下来开始实现。
减少判断
依据责任链的定义,每个办法都必须要晓得以后这件事是不是本人应该解决的,所以要把本来在 httpErrorHandler
实现的 if
判断扩散到每个办法中,变成由外部管制本人的责任:
const response400 = (error) => {if (error.response.status !== 400) return;
console.log('你是不是提交了什么奇怪的货色?');
};
const response401 = (error) => {if (error.response.status !== 401) return;
console.log('须要先登陆!');
};
const response403 = (error) => {if (error.response.status !== 403) return;
console.log('是不是想偷摸干坏事?');
};
const response404 = (error) => {if (error.response.status !== 404) return;
console.log('这里什么也没有...');
};
const httpErrorHandler = (error) => {response400(error);
response401(error);
response403(error);
response404(error);
};
把判断的逻辑放到各自的办法中之后,httpErrorHandler
的代码就精简了很多,也去除了所有在 httpErrorHandler
中的逻辑,当初 httpErrorHandler
只须要依照程序执行 response400
到 response404
就行了,反正该执行就执行,不该执行的也只是间接 return
而已。
实现真正的责任链
尽管只有重构到上一步,所有被分拆的错误处理办法都会自行判断以后是不是本人该做的,然而如果你的代码就这样了,那么未来看到 httpErrorHandler
的其他人只会说:
这是什么神仙代码?API 一遇到谬误就执行所有错误处理?
因为他们不晓得在每个解决办法外面还有判断,兴许过一段时间之后你本人也会忘了这事,因为当初的 httpErrorHandler
看起来就只是从 response400
到 response404
,即便咱们晓得性能正确,但齐全看不出是用了责任链。
那到底怎样才能看起来像是个链呢?其实你能够间接用一个数字记录所有要被执行的错误处理办法,并通过命名通知未来看到这段代码的人这里是责任链:
const httpErrorHandler = (error) => {
const errorHandlerChain = [
response400,
response401,
response403,
response404
];
errorHandlerChain.forEach((errorHandler) => {errorHandler(error);
});
};
优化执行
这样一来责任链的目标就有达到了,如果像下面代码中用 forEach
解决的话,那当遇到 400 谬误时,实际上是不须要执行前面的 response401
到 response404
的。
所以还要在每个错误处理的办法中加上一些逻辑,让每个办法能够判断,如果是遇到本人解决不了的事件,就丢出一个指定的字符串或布尔值,接管到之后就再接着执行下一个办法,但如果该办法能够解决,则在处理完毕之后间接完结,不须要再持续把整个链跑完。
const response400 = (error) => {if (error.response.status !== 400) return 'next';
console.log('你是不是提交了什么奇怪的货色?');
};
const response401 = (error) => {if (error.response.status !== 401) return 'next';
console.log('须要先登陆!');
};
const response403 = (error) => {if (error.response.status !== 403) return 'next';;
console.log('是不是想偷摸干坏事?');
};
const response404 = (error) => {if (error.response.status !== 404) return 'next';;
console.log('这里什么都没有...');
};
如果链中某个节点执行后果为 next
,则让下前面的办法持续解决:
const httpErrorHandler = (error) => {
const errorHandlerChain = [
response400,
response401,
response403,
response404
];
for(errorHandler of errorHandlerChain) {const result = errorHandler(error);
if (result !== 'next') break;
};
};
封装责任链的实现
当初责任链曾经实现实现了,然而判断要不要给下一个办法的逻辑(判断 result !== 'next'
),却裸露在里面,这兴许会导致我的项目中每个链的实现办法都会不一样,其余的链有可能是判断 nextSuccessor
或是 boolean
,所以最初还须要封装一下责任链的实现,让团队中的每个人都能够应用并且恪守我的项目中的标准。
责任链须要:
- 以后的执行者。
- 下一个的接收者。
- 判断以后执行者执行后是否须要交由下一个执行者。
所以封装成类当前应该是这样:
class Chain {constructor(handler) {
this.handler = handler;
this.successor = null;
}
setSuccessor(successor) {
this.successor = successor;
return this;
}
passRequest(...args) {const result = this.handler(...args);
if (result === 'next') {return this.successor && this.successor.passRequest(...args);
}
return result;
}
}
用 Chain
创建对象时须要将以后的职责办法传入并设置给 handler
,并且能够在新对象上用 setSuccessor
把链中的下一个对象指定给 successor
,在 setSuccessor
里返回代表整条链的 this
,这样在操作的时候能够间接在 setSuccessor
前面用 setSuccessor
设置下一个接收者。
最初,每个通过 Chain
产生的对象都会有 passRequest
来执行以后的职责办法,…arg
会把传入的所有参数变成一个数组,而后一起交给 handler
也就是以后的职责办法执行,如果返回的后果 result
是 next 的话,就去判断有没有指定 sucessor
如果有的话就继续执行,如果 result
不是 next,则间接返回 result
。
有了 Chain
后代码就会变成:
const httpErrorHandler = (error) => {const chainRequest400 = new Chain(response400);
const chainRequest401 = new Chain(response401);
const chainRequest403 = new Chain(response403);
const chainRequest404 = new Chain(response404);
chainRequest400.setSuccessor(chainRequest401);
chainRequest401.setSuccessor(chainRequest403);
chainRequest403.setSuccessor(chainRequest404);
chainRequest400.passRequest(error);
};
这时就很有链的感觉了,大家还能够再持续依据本人的需要做调整,或是也不肯定要应用类,因为设计模式的应用并不需要局限于如何实现,只有有表白出该模式的用意就够了。
责任链的优缺点
长处:
- 合乎繁多职责,使每个办法中都只有一个职责。
- 合乎凋谢关闭准则,在需要减少时能够很不便的裁减新的责任。
- 应用时候不须要晓得谁才是真正解决办法,缩小大量的
if
或switch
语法。
毛病:
- 团队成员须要对责任链存在共识,否则当看到一个办法莫名其妙的返回一个 next 时肯定会很奇怪。
- 出错时不好排查问题,因为不晓得到底在哪个责任中出的错,须要从链头开始往后找。
- 就算是不须要做任何解决的办法也会执行到,因为它在同一个链中,文中的例子都是同步执行的,如果有异步申请的话,执行工夫兴许就会比拟长。
与策略模式的不同
在后面我还提到过策略模式,先说说两个模式之间的类似处,那就是都能够替多个同一个行为(response400
、response401
等)定义一个接口(httpErrorHandler
),而且在应用时不须要晓得最初是谁执行的。在实现上策略模式比较简单。
因为策略模式间接用 if
或 switch
来管制谁该做这件事件,比拟适宜一个萝卜一个坑的情况。而策略模式尽管在例子中也是针对谬误的状态码做各自的事,都在不归本人管的时候间接把事交给下一位解决,然而在责任链中的每个节点依然能够在不归本人管的时候先做些什么,而后再交给下个节点:
const response400 = (error) => {if (error.response.status !== 400) {
// 先做点什么...
return 'next';
}
console.log('你是不是提交了什么奇怪的货色?');
};
那在什么场景下应用呢?比方有一天你感觉世界那么大,应该去看看,在到职时须要走一个签字流程:你本人、你的 Leader 还有人资都须要做签字这件事,所以责任链就能够把这三个角色的签字过程串成一个流程,每个人签过后都会交给上面一位,始终到人资签完后才实现整个流程。而且如果通过责任链解决这个流程,不管之后流程怎么变动或减少,都有方法进行弹性解决。
下面的例子的需要是策略模式所无奈胜任的。
本文首发微信公众号:前端先锋
欢送扫描二维码关注公众号,每天都给你推送陈腐的前端技术文章
欢送持续浏览本专栏其它高赞文章:
- 深刻了解 Shadow DOM v1
- 一步步教你用 WebVR 实现虚拟现实游戏
- 13 个帮你进步开发效率的古代 CSS 框架
- 疾速上手 BootstrapVue
- JavaScript 引擎是如何工作的?从调用栈到 Promise 你须要晓得的所有
- WebSocket 实战:在 Node 和 React 之间进行实时通信
- 对于 Git 的 20 个面试题
- 深刻解析 Node.js 的 console.log
- Node.js 到底是什么?
- 30 分钟用 Node.js 构建一个 API 服务器
- Javascript 的对象拷贝
- 程序员 30 岁前月薪达不到 30K,该何去何从
- 14 个最好的 JavaScript 数据可视化库
- 8 个给前端的顶级 VS Code 扩大插件
- Node.js 多线程齐全指南
- 把 HTML 转成 PDF 的 4 个计划及实现
- 更多文章 …