目的在开始学习责任链之前,我们先来看看开发中常见的问题。下面是前端用来处理API错误码的代码:consthttpErrorHandler=(error)=>{consterrorStatus=error.response.status;if(errorStatus===400){console.log('你提交了什么奇怪的东西吗?');}if(errorStatus===401){console.log('需要先登录!');}if(errorStatus===403){console.log('Areyoutryingtodosomethingsneakying??');}if(errorStatus===404){console.log('这里什么都没有...');}};当然实际项目中不可能只有一行console,这里是为了说明原理做的简化版。代码中的httpErrorHandler会收到API的响应错误并对错误状态码进行不同的处理,所以代码中需要大量的if(或switch)来判断当前需要执行什么,当你想添加处理代码到新的错误,需要修改httpErrorHandler中的代码。虽然频繁修改代码是不可避免的,但这样做可能会导致几个问题。以下是基于SOLID的单一职责(Singleresponsibility)和开闭(open/close)的原则:单一职责(Singleresponsibility)simple也就是说,单一职责就是只做一件事。从使用的角度来说,之前的httpErrorHandler方法就是把错误对象交给它,让它根据错误码来处理。看似是在做“错误处理”这件单一的事情,但是从实现的角度来说,它把不同错误的处理逻辑都写在了httpErrorHandler中,这可能会导致只修改400逻辑的错误码的可能性,但是必须阅读一大堆不相关的代码。开闭原则(open/close)开闭原则是指已经写好的核心逻辑不应该被改变,但同时必须能够扩展原有的功能,因为增加需求,即开启扩展功能,同时关闭修改原有的正确逻辑。回过头来看httpErrorHandler,如果需要对错误码405增加处理逻辑(扩展新功能),需要修改httpErrorHandler中的代码(修改原来正确的逻辑),这样很容易导致原来正确的执行错误代码。既然httpErrorHandler有这么多的缺陷,我们该怎么办呢?解决逻辑分离的问题,首先让httpErrorHandler符合单一原则。首先将每个错误的处理逻辑拆分成方法:constresponse400=()=>{console.log('Didyousubmitsomethingstrange?');};constresponse401=()=>{console.log('你需要先登录!');};constresponse403=()=>{console.log('你在偷偷摸摸吗?');};constresponse404=()=>{console.log('这里什么都没有...');};consthttpErrorHandler=(error)=>{consterrorStatus=error.response.status;if(errorStatus===400){response400();}if(errorStatus===401){response401();}if(errorStatus===403){response403();}if(errorStatus===404){response404();}};虽然只是将每个block的逻辑拆分成method,但这已经让我们可以修改某个状态码的错误处理,而无需阅读httpErrorHandler中的大量代码。只是分离逻辑的操作也让httpErrorHandler符合了开放封闭的原则,因为当错误处理的逻辑被拆分成方法时,就相当于封装了完成的代码。这时候需要在为httpErrorHandler添加405的错误处理逻辑时,不会影响其他错误处理逻辑方法(关闭修改),而是新建一个response405方法,为httpErrorHandler添加新的条件判断(打开扩展了新功能)。现在的httpErrorHandler其实就是一种策略模式。httpErrorHandler使用统一的接口(方法)来处理各种错误状态。文末再解释一下策略模式和责任链的区别。责任链模式(ChainofResponsibilityPattern)责任链的实现原理很简单,就是把所有的方法串起来,一个一个执行,每个方法只需要做它需要做的事情。例如,response400只遇到状态码。为400时执行,response401只处理401错误,其他方法只在该处理的时候才执行。每个人都各司其职,这就是责任链。然后开始实施。增加判断根据责任链的定义,每个方法都必须知道当前的事情是否应该由自己来处理,所以原本在httpErrorHandler中实现的if判断应该分发给每个方法,在内部控制责任:constresponse400=(error)=>{if(error.response.status!==400)return;console.log('你提交了一些奇怪的东西吗?');};constresponse401=(error)=>{if(error.response.status!==401)return;console.log('需要先登录!');};constresponse403=(error)=>{if(error.response.status!==403)return;console.log('Areyoutryingtodosomethingsneaky?');};constresponse404=(error)=>{if(error.response.status!==404)return;console.log('这里什么都没有...');};consthttpErrorHandler=(error)=>{response400(error);response401(错误);响应403(错误);response404(错误);};方法之后,httpErrorHandler的代码被简化了很多,去掉了httpErrorHandler中的所有逻辑。现在httpErrorHandler只需要依次执行response400到response404即可。反正该执行,不该执行的直接return就好了。就这样。实现真正的责任链虽然只要你重构到上一步,所有的split错误处理方法此刻都会判断自己是否应该去做,但是如果你的代码是这样的,那么以后其他人看到httpErrorHandlerwillonly会说:这是什么神仙密码?API是否一遇到错误就执行所有错误处理?因为他们不知道每个处理方法里面还有判断,也许过段时间你就会忘记了,因为现在的httpErrorHandler只是从response400到response404看,即使我们知道功能是正确的,但是完全看不到不可能使用责任链。那么它到底怎么看起来像一条链条呢?其实可以直接用一个数字记录所有要执行的错误处理方法,通过命名告诉以后看到这段代码的人这里是责任链:consthttpErrorHandler=(error)=>{consterrorHandlerChain=[response400,response401,response403,response404];errorHandlerChain.forEach((errorHandler)=>{errorHandler(error);});};这样,责任链的目的就达到了,如果像上面代码那样用forEach处理,遇到400错误的时候,其实不需要执行后面的response401到response404。所以需要在每个错误处理方法中加入一些逻辑,让每个方法都可以判断,如果遇到了自己无法处理的事情,就抛出一个指定的字符串或者布尔值,接收到之后再返回。然后执行下一个方法,但是如果这个方法可以处理,处理完就直接结束,不需要继续跑整个链条。constresponse400=(error)=>{if(error.response.status!==400)return'next';console.log('你提交了一些奇怪的东西吗?');};constresponse401=(error)=>{if(error.response.status!==401)return'next';console.log('需要先登录!');};constresponse403=(error)=>{if(error.response.status!==403)return'next';;console.log('Areyoutryingtodosomethingsneaky?');};constresponse404=(error)=>{if(error.response.status!==404)return'next';;console.log('这里什么都没有...');};如果链中某个节点的执行结果是next,则让next方法继续处理:for(errorHandlerChain的errorHandler){constresult=errorHandler(error);如果(结果!=='下一个')中断;};};现在责任链已经实现了,但是将判断是否给next方法(判断结果!=='next')的逻辑暴露在外,可能会导致项目中各个链的实现方法被不同的。同样的,其他链可能会判断nextSuccessor或者boolean,所以最后需要封装责任链的实现,让团队中的每个人都可以使用,并遵守项目中的规范。责任链要求:现任执行人。下一个收件人。判断当前执行者执行后是否需要交给下一个执行者。所以封装成类后应该是这样的:classChain{constructor(handler){this.handler=handler;this.successor=null;}setSuccessor(successor){this.successor=successor;归还这个;}passRequest(...args){constresult=this.handler(...args);if(result==='next'){returnthis.successor&&this.successor.passRequest(...args);}返回结果;}}用Chain创建对象时,需要传入当前的responsible方法并设置为handler,可以在新对象上使用setSuccessor将链中的下一个对象赋值给successor,返回this代表setSuccessor中的整个链。操作时,可以直接在setSuccessor后面使用setSuccessor来设置下一个接收者。最后通过Chain生成的每个对象都会有一个passRequest去执行当前职责方法,...arg会把传入的所有参数变成一个数组,然后交给handler,也就是当前职责方法,对于执行。如果返回的结果是下一个结果,则判断是否有指定的后继者。如果是,它将继续执行。如果结果不是next,则直接返回结果。使用Chain,代码变为:consthttpErrorHandler=(error)=>{constchainRequest400=newChain(response400);constchainRequest401=newChain(response401);constchainRequest403=newChain(response403);constchainRequest404=newChain(response404);chainRequest400.setSuccessor(chainRequest401);chainRequest401.setSuccessor(chainRequest403);chainRequest403.setSuccessor(chainRequest404);chainRequest400.passRequest(错误);};根据自己的需要进行调整,也可以不用类,因为设计模式的使用不需要局限于如何实现,只要有表达模式的意图即可。责任链的优点和缺点优点:符合单一责任,因此每个方法只有一个责任。它符合开放和封闭的原则,当需求增加时可以很容易地扩展新的职责。使用的时候不需要知道真正的处理方法是谁,减少了很多if或者switch的语法。缺点:团队成员需要对责任链有共识,否??则看到一个方法无缘无故返回一个next会很奇怪。出现问题时很难排查,因为你不知道是哪个责任造成的错误,所以需要从链条的头开始,向后看。即使不需要任何处理的方法也会被执行,因为它们在同一个链中。本文中的例子都是同步执行的。如果有异步请求,执行时间可能会更长。与策略模式的区别前面提到了策略模式,先说说两种模式的相同点,就是可以为多个相同的行为(response400、response401等)定义一个接口(httpErrorHandler),而当使用它,你不需要知道最后是谁执行的。策略模式实现起来相对简单。由于策略模式直接使用if或者switch来控制谁来做这件事情,比较适合一萝卜一坑的情况。虽然策略模式对于例子中错误的状态码也各干各的,当事情不在自己管控的时候会直接交给下一个人,但是责任链上的每个节点还是可以做一些事情的首先当你自己管理它,然后交给下一个节点:constresponse400=(error)=>{if(error.response.status!==400){//先做点什么...return'next';}console.log('你提交了一些奇怪的东西吗?');};那么在什么场景下使用呢?比如有一天你觉得世界那么大,你要去看看,离开的时候需要经过一个签字的过程:你,你的leader,人力资源都需要签字,所以链责任的可以把这三个角色结合起来,把公司的签约流程串成一个流程。每个人签字后,他们将把它交给下一个人。整个过程直到人员和资源被签下才算完成。而如果通过责任链来处理这个流程,那么无论后面这个流程如何变化或者增加,都有办法灵活处理。上面例子的要求是策略模型达不到的。
