自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

代碼越寫越亂?那是因?yàn)槟銢]用責(zé)任鏈

開發(fā) 前端
代碼中的 httpErrorHandler 會(huì)接收 API 的響應(yīng)錯(cuò)誤,并對(duì)錯(cuò)誤的狀態(tài)碼做不同的處理,所以代碼中需要很多 if(或者 switch)判斷當(dāng)前需要要執(zhí)行什么,當(dāng)你要對(duì)新的錯(cuò)誤添加處理代碼時(shí),就必須要到 httpErrorHandler 中修改代碼。

 [[396382]]

目的

在開始學(xué)習(xí)責(zé)任鏈之前,先看一下在開發(fā)中常見的問題。下面是前端用來處理 API 錯(cuò)誤碼的代碼:

 

  1.  const httpErrorHandler = (error) => { 
  2.    const errorStatus = error.response.status; 
  3.    if (errorStatus === 400) { 
  4.      console.log('你是不是提交了什么奇怪的東西?'); 
  5.    } 
  6.     
  7.    if (errorStatus === 401) { 
  8.      console.log('需要先登陸!'); 
  9.    } 
  10.     
  11.    if (errorStatus === 403) { 
  12.      console.log('是不是想偷摸干壞事?'); 
  13.    } 
  14.     
  15.    if (errorStatus === 404) { 
  16.      console.log('這里什么也沒有...'); 
  17.    } 
  18. }; 

當(dāng)然實(shí)際項(xiàng)目中不可能只有一行 console,這是為了說明原理的簡化版。

代碼中的 httpErrorHandler 會(huì)接收 API 的響應(yīng)錯(cuò)誤,并對(duì)錯(cuò)誤的狀態(tài)碼做不同的處理,所以代碼中需要很多 if(或者 switch)判斷當(dāng)前需要要執(zhí)行什么,當(dāng)你要對(duì)新的錯(cuò)誤添加處理代碼時(shí),就必須要到 httpErrorHandler 中修改代碼。

雖然免不了要經(jīng)常修改代碼,但是這樣做可能會(huì)導(dǎo)致幾個(gè)問題,下面根據(jù) SOLID 的 單一職責(zé)(Single responsibility)和開放封閉(open/close)這兩個(gè)原則來說明:

單一職責(zé)(Single responsibility)

簡單的說,單一職責(zé)就是只做一件事情。而前面的 httpErrorHandler 方法以使用的角度來說,是把錯(cuò)誤對(duì)象交給它,讓它按照錯(cuò)誤碼做對(duì)應(yīng)的處理。看上去好像是在做“錯(cuò)誤處理”這個(gè)單一的事情,但是從實(shí)現(xiàn)的角度上來說,它把不同錯(cuò)誤的處理邏輯全部寫在了 httpErrorHandler 中,這就會(huì)導(dǎo)致可能在只想要修改對(duì)錯(cuò)誤碼為 400 的邏輯時(shí),但是不得不閱讀一大堆不相關(guān)的代碼。

開放封閉原則(open/close)

開放封閉原則是指對(duì)已經(jīng)寫好的核心邏輯就不要再去改動(dòng),但同時(shí)要能夠因需求的增加而擴(kuò)充原本的功能,也就是開放擴(kuò)充功能,同時(shí)封閉修改原本正確的邏輯。再回過頭來看 httpErrorHandler,如果需要增加一個(gè)對(duì)錯(cuò)誤碼 405 的處理邏輯(要擴(kuò)充新功能),那就需要修改 httpErrorHandler 中的代碼(修改原本正確的邏輯),這也很容易造成原來正確執(zhí)行的代碼出錯(cuò)。

既然 httpErrorHandler 破綻這么多,那該怎么辦?

解決問題

分離邏輯

先讓 httpErrorHandler 符合單一原則。首先把每個(gè)錯(cuò)誤的處理邏輯分別拆成方法:

 

  1. const response400 = () => { 
  2.   console.log('你是不是提交了什么奇怪的東西?'); 
  3. }; 
  4.  
  5. const response401 = () => { 
  6.   console.log('需要先登陸!'); 
  7. }; 
  8.  
  9. const response403 = () => { 
  10.   console.log('是不是想偷摸干壞事?'); 
  11. }; 
  12.  
  13. const response404 = () => { 
  14.   console.log('這里什么也沒有...'); 
  15. }; 
  16.  
  17. const httpErrorHandler = (error) => { 
  18.   const errorStatus = error.response.status; 
  19.   if (errorStatus === 400) { 
  20.     response400(); 
  21.   } 
  22.     
  23.   if (errorStatus === 401) { 
  24.     response401(); 
  25.   } 
  26.     
  27.   if (errorStatus === 403) { 
  28.     response403(); 
  29.   } 
  30.     
  31.   if (errorStatus === 404) { 
  32.     response404(); 
  33.   } 
  34. }; 

雖然只是把每個(gè)區(qū)塊的邏輯拆成方法,但這已經(jīng)可以讓我們在修改某個(gè)狀態(tài)碼的錯(cuò)誤處理時(shí),不用再到 httpErrorHandler 中閱讀大量的代碼了。

僅僅是分離邏輯這個(gè)操作同時(shí)也讓 httpErrorHandler 符合了開放封閉原則,因?yàn)樵诎彦e(cuò)誤處理的邏輯各自拆分為方法的時(shí)候,就等于對(duì)那些已經(jīng)完成的代碼進(jìn)行了封裝,這時(shí)當(dāng)需要再為 httpErrorHandler 增加對(duì) 405 的錯(cuò)誤處理邏輯時(shí),就不會(huì)影響到其他的錯(cuò)誤處理邏輯的方法(封閉修改),而是另行創(chuàng)建一個(gè)新的 response405 方法,并在 httpErrorHandler 中加上新的條件判斷就行了(開放擴(kuò)充新功能)。

現(xiàn)在的 httpErrorHandler 其實(shí)是策略模式(strategy pattern),httpErrorHandler 用了統(tǒng)一的接口(方法)來處理各種不同的錯(cuò)誤狀態(tài),在本文的最后會(huì)再次解釋策略模式和責(zé)任鏈之間的區(qū)別。

責(zé)任鏈模式(Chain of Responsibility Pattern)責(zé)任鏈的實(shí)現(xiàn)

原理很簡單,就是把所有方法串起來一個(gè)一個(gè)執(zhí)行,并且每個(gè)方法都只做自己要做的事就行了,例如 response400 只在遇到狀態(tài)碼為 400 的時(shí)候執(zhí)行,而 response401 只處理 401 的錯(cuò)誤,其他方法也都只在自己該處理的時(shí)候執(zhí)行。每個(gè)人各司其職,就是責(zé)任鏈。

接下來開始實(shí)現(xiàn)。

增加判斷

根據(jù)責(zé)任鏈的定義,每個(gè)方法都必須要知道當(dāng)前這件事是不是自己應(yīng)該處理的,所以要把原本在 httpErrorHandler 實(shí)現(xiàn)的 if 判斷分散到每個(gè)方法中,變成由內(nèi)部控制自己的責(zé)任:

 

  1. const response400 = (error) => { 
  2.   if (error.response.status !== 400) return
  3.   console.log('你是不是提交了什么奇怪的東西?'); 
  4. }; 
  5.  
  6. const response401 = (error) => { 
  7.   if (error.response.status !== 401) return
  8.   console.log('需要先登陸!'); 
  9. }; 
  10.  
  11. const response403 = (error) => { 
  12.   if (error.response.status !== 403) return
  13.   console.log('是不是想偷摸干壞事?'); 
  14. }; 
  15.  
  16. const response404 = (error) => { 
  17.   if (error.response.status !== 404) return
  18.   console.log('這里什么也沒有...'); 
  19. }; 
  20.  
  21. const httpErrorHandler = (error) => { 
  22.   response400(error); 
  23.   response401(error); 
  24.   response403(error); 
  25.   response404(error); 
  26. }; 

把判斷的邏輯放到各自的方法中之后,httpErrorHandler 的代碼就精簡了很多,也去除了所有在 httpErrorHandler 中的邏輯,現(xiàn)在httpErrorHandler 只需要按照順序執(zhí)行 response400 到 response404 就行了,反正該執(zhí)行就執(zhí)行,不該執(zhí)行的也只是直接 return 而已。

實(shí)現(xiàn)真正的責(zé)任鏈

雖然只要重構(gòu)到上一步,所有被分拆的錯(cuò)誤處理方法都會(huì)自行判斷當(dāng)前是不是自己該做的,但是如果你的代碼就這樣了,那么將來看到 httpErrorHandler 的其他人只會(huì)說:

這是什么神仙代碼?API 一遇到錯(cuò)誤就執(zhí)行所有錯(cuò)誤處理?

因?yàn)樗麄儾恢涝诿總€(gè)處理方法里面還有判斷,也許過一段時(shí)間之后你自己也會(huì)忘了這事,因?yàn)楝F(xiàn)在的 httpErrorHandler 看起來就只是從 response400 到 response404,即使我們知道功能正確,但完全看不出是用了責(zé)任鏈。

那到底怎樣才能看起來像是個(gè)鏈呢?其實(shí)你可以直接用一個(gè)數(shù)字記錄所有要被執(zhí)行的錯(cuò)誤處理方法,并通過命名告訴將來看到這段代碼的人這里是責(zé)任鏈:

 

  1. const httpErrorHandler = (error) => { 
  2.   const errorHandlerChain = [ 
  3.     response400, 
  4.     response401, 
  5.     response403, 
  6.     response404 
  7.   ]; 
  8.   errorHandlerChain.forEach((errorHandler) => { 
  9.     errorHandler(error); 
  10.   }); 
  11. }; 

優(yōu)化執(zhí)行

這樣一來責(zé)任鏈的目的就有達(dá)到了,如果像上面代碼中用 forEach 處理的話,那當(dāng)遇到 400 錯(cuò)誤時(shí),實(shí)際上是不需要執(zhí)行后面的 response401 到 response404 的。

所以還要在每個(gè)錯(cuò)誤處理的方法中加上一些邏輯,讓每個(gè)方法可以判斷,如果是遇到自己處理不了的事情,就丟出一個(gè)指定的字符串或布爾值,接收到之后就再接著執(zhí)行下一個(gè)方法,但如果該方法可以處理,則在處理完畢之后直接結(jié)束,不需要再繼續(xù)把整個(gè)鏈跑完。

 

  1. const response400 = (error) => { 
  2.   if (error.response.status !== 400) return 'next'
  3.   console.log('你是不是提交了什么奇怪的東西?'); 
  4. }; 
  5.  
  6. const response401 = (error) => { 
  7.   if (error.response.status !== 401) return 'next'
  8.   console.log('需要先登陸!'); 
  9. }; 
  10.  
  11. const response403 = (error) => { 
  12.   if (error.response.status !== 403) return 'next';; 
  13.   console.log('是不是想偷摸干壞事?'); 
  14. }; 
  15.  
  16. const response404 = (error) => { 
  17.   if (error.response.status !== 404) return 'next';; 
  18.   console.log('這里什么都沒有...'); 
  19. }; 

如果鏈中某個(gè)節(jié)點(diǎn)執(zhí)行結(jié)果為 next,則讓下后面的方法繼續(xù)處理:

 

  1. const httpErrorHandler = (error) => { 
  2.   const errorHandlerChain = [ 
  3.     response400, 
  4.     response401, 
  5.     response403, 
  6.     response404 
  7.   ]; 
  8.    
  9.   for(errorHandler of errorHandlerChain) { 
  10.     const result = errorHandler(error); 
  11.     if (result !== 'next') break; 
  12.   }; 
  13. }; 

封裝責(zé)任鏈的實(shí)現(xiàn)

現(xiàn)在責(zé)任鏈已經(jīng)實(shí)現(xiàn)完成了,但是判斷要不要給下一個(gè)方法的邏輯(判斷 result !== 'next') ,卻暴露在外面,這也許會(huì)導(dǎo)致項(xiàng)目中每個(gè)鏈的實(shí)現(xiàn)方法都會(huì)不一樣,其他的鏈有可能是判斷 nextSuccessor 或是 boolean,所以最后還需要封裝一下責(zé)任鏈的實(shí)現(xiàn),讓團(tuán)隊(duì)中的每個(gè)人都可以使用并且遵守項(xiàng)目中的規(guī)范。

責(zé)任鏈需要:

  1. 當(dāng)前的執(zhí)行者。
  2. 下一個(gè)的接收者。
  3. 判斷當(dāng)前執(zhí)行者執(zhí)行后是否需要交由下一個(gè)執(zhí)行者。

所以封裝成類以后應(yīng)該是這樣:

 

  1. class Chain { 
  2.   constructor(handler) { 
  3.     this.handler = handler; 
  4.     this.successor = null
  5.   } 
  6.  
  7.   setSuccessor(successor) { 
  8.     this.successor = successor; 
  9.     return this; 
  10.   } 
  11.  
  12.   passRequest(...args) { 
  13.     const result = this.handler(...args); 
  14.     if (result === 'next') { 
  15.       return this.successor && this.successor.passRequest(...args); 
  16.     } 
  17.     return result; 
  18.   } 

用 Chain 創(chuàng)建對(duì)象時(shí)需要將當(dāng)前的職責(zé)方法傳入并設(shè)置給 handler,并且可以在新對(duì)象上用 setSuccessor 把鏈中的下一個(gè)對(duì)象指定給 successor,在 setSuccessor 里返回代表整條鏈的 this,這樣在操作的時(shí)候可以直接在 setSuccessor 后面用 setSuccessor 設(shè)置下一個(gè)接收者。

最后,每個(gè)通過 Chain 產(chǎn)生的對(duì)象都會(huì)有 passRequest 來執(zhí)行當(dāng)前的職責(zé)方法,…arg 會(huì)把傳入的所有參數(shù)變成一個(gè)數(shù)組,然后一起交給 handler 也就是當(dāng)前的職責(zé)方法執(zhí)行,如果返回的結(jié)果 result 是 next 的話,就去判斷有沒有指定 sucessor 如果有的話就繼續(xù)執(zhí)行,如果 result 不是 next,則直接返回 result。

有了 Chain 后代碼就會(huì)變成:

 

  1. const httpErrorHandler = (error) => { 
  2.   const chainRequest400 = new Chain(response400); 
  3.   const chainRequest401 = new Chain(response401); 
  4.   const chainRequest403 = new Chain(response403); 
  5.   const chainRequest404 = new Chain(response404); 
  6.  
  7.   chainRequest400.setSuccessor(chainRequest401); 
  8.   chainRequest401.setSuccessor(chainRequest403); 
  9.   chainRequest403.setSuccessor(chainRequest404); 
  10.  
  11.   chainRequest400.passRequest(error); 
  12. }; 

這時(shí)就很有鏈的感覺了,大家還可以再繼續(xù)根據(jù)自己的需求做調(diào)整,或是也不一定要使用類,因?yàn)樵O(shè)計(jì)模式的使用并不需要局限于如何實(shí)現(xiàn),只要有表達(dá)出該模式的意圖就夠了。

責(zé)任鏈的優(yōu)缺點(diǎn)

優(yōu)點(diǎn):

符合單一職責(zé),使每個(gè)方法中都只有一個(gè)職責(zé)。

符合開放封閉原則,在需求增加時(shí)可以很方便的擴(kuò)充新的責(zé)任。

使用時(shí)候不需要知道誰才是真正處理方法,減少大量的 if 或 switch 語法。

缺點(diǎn):

團(tuán)隊(duì)成員需要對(duì)責(zé)任鏈存在共識(shí),否則當(dāng)看到一個(gè)方法莫名其妙的返回一個(gè) next 時(shí)一定會(huì)很奇怪。

出錯(cuò)時(shí)不好排查問題,因?yàn)椴恢赖降自谀膫€(gè)責(zé)任中出的錯(cuò),需要從鏈頭開始往后找。

就算是不需要做任何處理的方法也會(huì)執(zhí)行到,因?yàn)樗谕粋€(gè)鏈中,文中的例子都是同步執(zhí)行的,如果有異步請求的話,執(zhí)行時(shí)間也許就會(huì)比較長。

與策略模式的不同

在前面我還提到過策略模式,先說說兩個(gè)模式之間的相似處,那就是都可以替多個(gè)同一個(gè)行為(response400、response401 等)定義一個(gè)接口(httpErrorHandler),而且在使用時(shí)不需要知道最后是誰執(zhí)行的。在實(shí)現(xiàn)上策略模式比較簡單。

由于策略模式直接用 if 或 switch 來控制誰該做這件事情,比較適合一個(gè)蘿卜一個(gè)坑的狀況。而策略模式雖然在例子中也是針對(duì)錯(cuò)誤的狀態(tài)碼做各自的事,都在不歸自己管的時(shí)候直接把事交給下一位處理,但是在責(zé)任鏈中的每個(gè)節(jié)點(diǎn)仍然可以在不歸自己管的時(shí)候先做些什么,然后再交給下個(gè)節(jié)點(diǎn):

 

  1. const response400 = (error) => { 
  2.   if (error.response.status !== 400) { 
  3.     // 先做點(diǎn)什么... 
  4.     return 'next'
  5.   } 
  6.   console.log('你是不是提交了什么奇怪的東西?'); 
  7. }; 

那在什么場景下使用呢?

 

 

 

[[396383]]

 

比如在離職時(shí)需要走一個(gè)簽字流程:你自己、你的 Leader 還有人資都需要做簽字這件事,所以責(zé)任鏈就可以把這三個(gè)角色的簽字過程串成一個(gè)流程,每個(gè)人簽過后都會(huì)交給下面一位,一直到人資簽完后才完成整個(gè)流程。而且如果通過責(zé)任鏈處理這個(gè)流程,不論之后流程怎樣變動(dòng)或增加,都有辦法進(jìn)行彈性處理。

上面的需求是策略模式所無法勝任的。

責(zé)任編輯:華軒 來源: 前端先鋒
相關(guān)推薦

2022-07-29 08:40:20

設(shè)計(jì)模式責(zé)任鏈場景

2021-09-06 18:54:58

Java代碼表達(dá)式

2025-02-06 07:30:32

2018-07-31 14:03:09

JVM內(nèi)存數(shù)據(jù)

2019-12-26 14:50:36

ORDER BY數(shù)據(jù)庫排序函數(shù)

2021-01-18 11:09:42

區(qū)塊鏈比特幣工具

2022-12-12 09:46:49

Kubernetes容器

2021-06-09 10:59:13

數(shù)字化轉(zhuǎn)型CIO數(shù)字化

2021-05-13 09:27:13

JavaThreadLocal線程

2023-06-26 07:05:12

IntelWindows電腦

2015-05-13 09:52:29

程序員代碼

2018-05-05 08:54:24

2018-04-24 10:29:40

2021-02-22 11:00:39

機(jī)器學(xué)習(xí)人工智能AI

2009-11-26 10:15:00

IT職場

2015-04-14 10:39:09

iWatch蘋果

2021-12-23 23:04:54

手機(jī)蘋果國產(chǎn)

2021-06-15 06:56:17

安卓系統(tǒng)應(yīng)用手機(jī)卡頓

2020-07-29 10:02:47

Java內(nèi)存故障內(nèi)存

2020-07-27 08:08:47

Java內(nèi)存JVM
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)