面試官:說(shuō)說(shuō)你對(duì)責(zé)任鏈模式的理解?應(yīng)用場(chǎng)景?
一、是什么
責(zé)任鏈模式(Chain of Responsibility Pattern)就是某個(gè)請(qǐng)求需要多個(gè)對(duì)象進(jìn)行處理,從而避免請(qǐng)求的發(fā)送者和接收之間的耦合關(guān)系
將這些對(duì)象連成一條鏈子,并沿著這條鏈子傳遞該請(qǐng)求,直到有對(duì)象處理它為止
職責(zé)鏈上的處理者負(fù)責(zé)處理請(qǐng)求,客戶只需要將請(qǐng)求發(fā)送到職責(zé)鏈上即可,無(wú)須關(guān)心請(qǐng)求的處理細(xì)節(jié)和請(qǐng)求的傳遞
常見(jiàn)的流程如下:
- 發(fā)送者知道鏈中的第一個(gè)接受者,它向這個(gè)接受者發(fā)出請(qǐng)求
- 每一個(gè)接受者都對(duì)請(qǐng)求進(jìn)行分析,要么處理它,要么往下傳遞
- 每一個(gè)接受者知道的其他對(duì)象只有一個(gè),即它的下家對(duì)象
- 如果沒(méi)有任何接受者處理請(qǐng)求,那么請(qǐng)求將從鏈上離開(kāi),不同的實(shí)現(xiàn)對(duì)此有不同的反應(yīng)
二、使用
假設(shè)我們負(fù)責(zé)一個(gè)售賣(mài)手機(jī)的網(wǎng)站,需求的定義是:需經(jīng)過(guò)分別繳納500元定金和200元定金的兩輪預(yù)訂,才能到正式購(gòu)買(mǎi)階段
公司對(duì)于交了定金的用戶有一定的優(yōu)惠政策,規(guī)則如下:
- 繳納500元定金的用戶可以收到100元優(yōu)惠券
- 納200元定金的用戶可以收到50元優(yōu)惠券
- 而沒(méi)有繳納定金的用戶進(jìn)入普通購(gòu)買(mǎi)模式,沒(méi)有優(yōu)惠券,而且在庫(kù)存不足的情況下,不一定能保證買(mǎi)得到
下面開(kāi)始設(shè)計(jì)幾個(gè)字段,解釋它們的含義:
- orderType:表示訂單類(lèi)型,值為1表示500元定金用戶,值為2表示200元定金用戶,值為3表示普通用戶。
- pay:表示用戶是否支付定金,值為布爾值true和false,就算用戶下了500元定金的訂單,但是如果沒(méi)有支付定金,那也會(huì)降級(jí)為普通用戶購(gòu)買(mǎi)模式。
- stock:表示當(dāng)前用戶普通購(gòu)買(mǎi)的手機(jī)庫(kù)存數(shù)量,已經(jīng)支付過(guò)定金的用戶不受限制。
代碼實(shí)現(xiàn)如下:
- const order = function (orderType, pay, stock) {
- if (orderType === 1) {
- if (pay === true) {
- console.log('500元定金預(yù)購(gòu),得到100元優(yōu)惠券')
- } else {
- if (stock > 0) {
- console.log('普通用戶購(gòu)買(mǎi),無(wú)優(yōu)惠券')
- } else {
- console.log('手機(jī)庫(kù)存不足')
- }
- } else if (orderType === 2) {
- if (pay === true) {
- console.log('200元定金預(yù)購(gòu),得到50元優(yōu)惠券')
- } else {
- if (stock > 0) {
- console.log('普通用戶購(gòu)買(mǎi),無(wú)優(yōu)惠券')
- } else {
- console.log('手機(jī)庫(kù)存不足')
- }
- }
- } else if (orderType === 3) {
- if (stock > 0) {
- console.log('普通用戶購(gòu)買(mǎi),無(wú)優(yōu)惠券')
- } else {
- console.log('手機(jī)庫(kù)存不足')
- }
- }
- }
- order(1, true, 500) // 輸出:500元定金預(yù)購(gòu),得到100元優(yōu)惠券'
可以看到上述代碼大量實(shí)用化if...else,難以閱讀,維護(hù)起來(lái)也很困難
如果進(jìn)行優(yōu)化,則可以把500元訂單、200元訂單以及普通購(gòu)買(mǎi)拆分成三個(gè)函數(shù),如下:
- function order500 (orderType, pay, stock) {
- if (orderType === 1 && pay === true) {
- console.log('500元定金預(yù)購(gòu),得到100元優(yōu)惠券')
- } else {
- order200(orderType, pay, stock)
- }
- }
- function order200 (orderType, pay, stock) {
- if (orderType === 2 && pay === true) {
- console.log('200元定金預(yù)購(gòu),得到50元優(yōu)惠券')
- } else {
- order200(orderType, pay, stock)
- }
- }
- function orderNormal (orderType, pay, stock) {
- if (stock > 0) {
- console.log('普通用戶購(gòu)買(mǎi),無(wú)優(yōu)惠券')
- } else {
- console.log('手機(jī)庫(kù)存不足')
- }
- }
- // 測(cè)試
- order500(1, true, 500) // 500元定金預(yù)購(gòu),得到100元優(yōu)惠券
- order500(1, false, 500) // 普通用戶購(gòu)買(mǎi),無(wú)優(yōu)惠券
- order500(2, true, 500) // 200元定金預(yù)購(gòu),得到50元優(yōu)惠券
- order500(3, false, 500) // 普通用戶購(gòu)買(mǎi),無(wú)優(yōu)惠券
- order500(3, false, 0) // 手機(jī)庫(kù)存不足
上述過(guò)程中,請(qǐng)求在鏈條中傳遞的順序很僵硬,傳遞請(qǐng)求的代碼跟業(yè)務(wù)代碼耦合在一起,如果有一天要增加300元定金的預(yù)訂,那么就要切斷之前的鏈條,修改訂單500函數(shù)的代碼,重新在500和200之間加一根新的鏈條,這違反了開(kāi)放-封閉原則
因此需要靈活更改責(zé)任鏈節(jié)點(diǎn),如果不能處理的時(shí)候,則返回一個(gè)標(biāo)識(shí)繼續(xù)往后傳遞,如下:
- function order500 (orderType, pay, stock) {
- if (orderType === 1 && pay === true) {
- console.log('500元定金預(yù)購(gòu),得到100元優(yōu)惠券')
- } else {
- return 'nextSuccessor'
- }
- }
- function order200 (orderType, pay, stock) {
- if (orderType === 2 && pay === true) {
- console.log('200元定金預(yù)購(gòu),得到50元優(yōu)惠券')
- } else {
- return 'nextSuccessor'
- }
- }
- function orderNormal (orderType, pay, stock) {
- if (stock > 0) {
- console.log('普通用戶購(gòu)買(mǎi),無(wú)優(yōu)惠券')
- } else {
- console.log('手機(jī)庫(kù)存不足')
- }
- }
下面再創(chuàng)建一個(gè)鏈類(lèi),將訂單優(yōu)惠函數(shù)傳入鏈類(lèi)中,如下:
- class Chain {
- construct (fn) {
- this.fn = fn
- this.successor = null
- }
- setNextSuccessor (successor) {
- return this.successor = successor
- }
- passRequest () {
- const res = this.fn.apply(this, arguments)
- if (res === 'nextSuccessor') {
- return this.successor && this.successor.passRequest.apply(this.successor, arguments)
- }
- return res
- }
- }
- // 包裝三個(gè)訂單函數(shù)
- const chainOrder500 = new Chain(order500)
- const chainOrder200 = new Chain(order200)
- const chainOrderNormal = new Chain(orderNormal)
- // 指定節(jié)點(diǎn)在職責(zé)鏈中的位置
- chainOrder500.setNextSuccessor(chainOrder200)
- chainOrder200.setNextSuccessor(chainOrderNormal)
- // 最后把請(qǐng)求傳遞給第一個(gè)節(jié)點(diǎn)
- chainOrder500.passRequest(1, true, 500) // 500元定金預(yù)購(gòu),得到100元優(yōu)惠券
- chainOrder500.passRequest(2, true, 500) // 200元定金預(yù)購(gòu),得到50元優(yōu)惠券
- chainOrder500.passRequest(3, true, 500) // 普通用戶購(gòu)買(mǎi),無(wú)優(yōu)惠券
- chainOrder500.passRequest(1, false, 0) // 手機(jī)庫(kù)存不足
三、應(yīng)用場(chǎng)景
責(zé)任鏈模式比較適合比如一個(gè)任務(wù)需要多個(gè)對(duì)象才能完成處理的情況或者代碼存在許多if-else判斷的情況,例如OA事件審批、分配開(kāi)發(fā)任務(wù)等
在JavaScript中,無(wú)論是作用鏈、原型鏈,還是DOM節(jié)點(diǎn)中的事件冒泡,我們都能從中找到職責(zé)鏈的影子
使用了職責(zé)鏈模式之后,鏈中的節(jié)點(diǎn)對(duì)象可以靈活地拆分重組,增加、刪除和修改節(jié)點(diǎn)在鏈中的位置都是很容易的事
參考文獻(xiàn)
https://www.runoob.com/design-pattern/chain-of-responsibility-pattern.html
https://juejin.cn/post/6993948920929845279
https://juejin.cn/post/6844903855348514829