您好,登錄后才能下訂單哦!
本篇文章為大家展示了為什么web開發中的代碼越寫越亂,內容簡明扼要并且容易理解,絕對能使你眼前一亮,通過這篇文章的詳細介紹希望你能有所收獲。
在開始學習責任鏈之前,先看一下在開發中常見的問題。下面是前端用來處理 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)這兩個原則來說明:
簡單的說,單一職責就是只做一件事情。而前面的 httpErrorHandler 方法以使用的角度來說,是把錯誤對象交給它,讓它按照錯誤碼做對應的處理。看上去好像是在做“錯誤處理”這個單一的事情,但是從實現的角度上來說,它把不同錯誤的處理邏輯全部寫在了 httpErrorHandler 中,這就會導致可能在只想要修改對錯誤碼為 400 的邏輯時,但是不得不閱讀一大堆不相關的代碼。
開放封閉原則是指對已經寫好的核心邏輯就不要再去改動,但同時要能夠因需求的增加而擴充原本的功能,也就是開放擴充功能,同時封閉修改原本正確的邏輯。再回過頭來看 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 用了統一的接口(方法)來處理各種不同的錯誤狀態,在本文的最后會再次解釋策略模式和責任鏈之間的區別。
原理很簡單,就是把所有方法串起來一個一個執行,并且每個方法都只做自己要做的事就行了,例如 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 還有人資都需要做簽字這件事,所以責任鏈就可以把這三個角色的簽字過程串成一個流程,每個人簽過后都會交給下面一位,一直到人資簽完后才完成整個流程。而且如果通過責任鏈處理這個流程,不論之后流程怎樣變動或增加,都有辦法進行彈性處理。
上面的需求是策略模式所無法勝任的。
上述內容就是為什么web開發中的代碼越寫越亂,你們學到知識或技能了嗎?如果還想學到更多技能或者豐富自己的知識儲備,歡迎關注億速云行業資訊頻道。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。