【Javascript設計模式】責任鍊模式

使多個對像都有機會處理請求,從而避免請求的發送者和接收者之間的耦合關係,將這些對象連成一條鏈,並沿著這條鏈傳遞該請求,直到有一個對象能處理它為止,傳遞鏈中的這些對象就叫節點。

在實際的運用當中,責任鍊模式也常常用在重構繁瑣的if… if else… 中,這和之前學習的策略模式有相似之處,相比策略,責任鍊模式邏輯上更加繁瑣,但是責任鍊模式有著更大的彈性空間。
假如上一個環節的結果,是需要交給下一個環節來繼續加工,那麼此時責任鍊模式是更加適合處理的。
接下來假設一個需求

一個電商網站,
用戶交500定金且定金已付時,可享受500優惠券且不受貨物數量限制;
用戶交200定金且定金已付時,可享受500優惠券且不受貨物數量限制;
用戶不交定金時受貨物數量限制,有貨時原價買,無貨時則無法買。

var buyOrder = function(orederType, pay, stock){if(orederType == 1){if(pay){console.log('500优惠券');} else {if(stock > 0){console.log('普通购物页面');} else {console.log('已无货');}}} else if(orederType == 2){if(pay){console.log('200优惠券');} else {if(stock > 0){console.log('普通购物页面');} else {console.log('已无货');}}} else if(orederType == 3){if(stock > 0){console.log('普通购物页面');} else {console.log('已无货');}}
}
buyOrder(1, true, 600)

接下來調用它

//3个订单函数 ,它们都是节点函数
const order500 = function (orderType, pay, stock) {if (orderType == '1' && pay == true) {console.log('500优惠券');} else {return 'nextSuccessor';     // 不管下一個節點是誰,反正就是把請求往後傳遞}
}
const order200 = function (orderType, pay, stock) {if (orderType == '2' && pay == true) {console.log('200优惠券');} else {return 'nextSuccessor';     // 不管下一個節點是誰,反正就是把請求往後傳遞}
}
const orderNormal = function (orderType, pay, stock) {if (stock > 0) {console.log('普通购物页面');} else {console.log('已无货');}
}
//把3個訂單函數分別包裝成職責鏈的節點
const chainOrder500 = new Chain(order500)
const chainOrder200 = new Chain(order200)
const chainOrderNormal = new Chain(orderNormal)//然後指定節點在職責鏈中的順序
chainOrder500.setNextSuccessor(chainOrder200)
chainOrder200.setNextSuccessor(chainOrderNormal)//最後把請求傳遞給第一個節點,開啟職責鏈模式傳遞
chainOrder500.handleRequest(1, true, 500)     //500优惠券
chainOrder500.handleRequest(3, true, 20)      //普通购物页面
chainOrder500.handleRequest(3, true, 0)       //已无货

之後來分享一下另外一個例子:
有一個活動的H5頁面,頁面被用在多個瀏覽器環境,比如微信、QQ、自己開發的app、其他第三方的app等等等等。由於後台接口對不同環境登錄態的處理不同,需要判斷不同環境,取cookie裡面的不同值,傳不同的登錄態token,有時候還要針對某些特殊環境,對網絡請求有一些特殊處理。

以下為typescript的例子,可以看到BaseHandler 這個類別是抽象類,表示它必須的依附在另外一個實例當中

  // 请求拦截器责任链节点抽象类
export default abstract class BaseHandler {// 下一个节点private nextHandler: BaseHandler | null = null;// 设置下一个节点public setNextHandler (handler: BaseHandler): BaseHandler {this.nextHandler = handler;return this.nextHandler;}// 调用该节点的方法处理请求,处理后,调用下一节点继续处理public handleRequest (config: any): void {// 当前节点的处理if (this.checkHandler(config)) {this.handler(config);}// 继续执行下一个节点if (this.nextHandler) {this.nextHandler.handlerRequest(config);}}// 调用该节点的方法处理请求,有一个节点处理就直接退出(互斥的)public handleRequestOnce(config: any): void {// 当前节点的处理if (this.checkHandle(config)) {this.handler(config);} else if (this.nextHandler) {this.nextHandler.handleRequestOnce(config);}}// 判断是否在这个节点处理abstract checkHandle(config: any): boolean;// 处理的方式abstract handler(config: any): void;
}
/*作者:lucio来全学了
链接:https://juejin.cn/post/7055149674650402824
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
*/
import BaseHandler from '../../base-handler';
import { getUrlPara } from '../../../url';
import { env } from '../../../env';// 处理小程序内嵌H5的请求参数
export default class WechatMpParamsHandler extends BaseHandler {// 在王者人生小程序,且为QQ登录,才用小程序登录态,其他情况下都用微信登录态checkHandle(): boolean {return env.isMiniProgram;}handler(config: any): any {if (getUrlPara('_ticket')) {config.params._ticket = getUrlPara('_ticket');}return config;}
}
/*
作者:lucio来全学了
链接:https://juejin.cn/post/7055149674650402824
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处
*/

最後在axios攔截器實現調用

// 请求拦截器
const requestInterceptor =  function (config) {const requestHandlerChain = new UrlInterceptor(); // 请求的责任链节点:CGI的URL处理requestHandlerChain.setNextHandler(new ParamsInterceptor()); // 请求的责任链节点:CGI的参数处理requestHandlerChain.handleRequest(config);return config;
};// 响应拦截器
const responseInterceptor = (response) => {const responseHandlerChain = new ParseDataInterceptor(); // 返回数据的责任链节点:解析新老框架的数据responseHandlerChain.setNextHandler(new LoginInfoInterceptor()) // 返回数据的责任链节点:处理登录信息.setNextHandler(new ErrorToastInterceptor()); // 返回数据的责任链节点:处理错误提示responseHandlerChain.handleRequest(response);return response;
};// 请求拦截器
axiosInstance.interceptors.request.use(requestInterceptor, error => Promise.reject(error));// 响应拦截器
axiosInstance.interceptors.response.use(responseInterceptor, error => Promise.reject(error));
/*
作者:lucio来全学了
链接:https://juejin.cn/post/7055149674650402824
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
*/

以上待續…