業(yè)務場景
項目是一個單頁面web應用乳愉,有一個基礎的websocket服務励翼,用于和服務器通信咸灿。剛開始主要有兩個作用:
- 賬號防重復登陸野建。當賬號在另外的地方登陸時,websocket收到服務器消息纪铺,網站即時彈窗提示用戶“當前賬號在另外一個地方登陸”,然后網站清除登錄信息并跳轉到登錄頁面。
- 當打開送貨管理界面時铣鹏,若有新的送貨請求,websocket收到服務器消息哀蘑,即時提示用戶“您有新的送貨請求吝沫,請及時處理”呻澜。若沒有打開送貨管理界面,不提示惨险。
起初是這樣的實現的:
//BasciWebsocket.js
websocket.onmessage = function (event) {
try{
let {data} = event;
let _data = JSON.parse(data);
switch(_data.type){
case 100:
Modal.warning({
title: '賬號登錄提醒',
content: "您的賬號已經在另外的地方進行登陸,請確認是否您本人操作羹幸。如有異常,請聯(lián)系公司系統(tǒng)管理員。",
okText: '知道了',
onOk: ()=> {
tool.clearUserCookie();
window.location = "/login";
}
});
break;
case 101:
let isPageShow = tool.ifDeliveryPageShow();
if(isPageShow){
//提示“您有新的送貨請求辫愉,請及時處理”
}
break;
}
}catch(e){
}
};
看起來并沒有什么問題栅受。但是,隨著項目越來越復雜恭朗,websocket負責通知的消息也越來越豐富屏镊。接收到消息后,往往還要改變具體的頁面展現痰腮,跟具體的頁面的關聯(lián)性非常大而芥。比如,用戶停留在訂單詳情頁時膀值,如果收到新的訂單反饋棍丐,那么訂單詳情頁的評論列表區(qū)域就要動畫出現新的反饋信息。
這個時候沧踏,你會發(fā)現歌逢,把這類跟具體頁面強關聯(lián)的邏輯寫在基礎的BasciWebsocket.js
文件里不科學,BasciWebsocket.js
也變得很臃腫翘狱,維護起來有些費勁秘案。問題就在于基礎的websocket服務與每個頁面的消息處理邏輯不夠解耦。
不難看出潦匈,在這個業(yè)務場景里阱高,websocket服務其實就是一個消息發(fā)布者,而這些具體頁面則是消息訂閱者茬缩。很自然地讨惩,我想到了一根救命稻草——經典的發(fā)布-訂閱模式(又叫觀察者模式)。
觀察者模式
觀察者模式定義對象間的一種一對多的依賴關系寒屯,當一個對象的狀態(tài)發(fā)生改變時荐捻,所有依賴它的對象都將得到通知。
如何實現觀察者模式寡夹?
- 指定好誰充當發(fā)布者(websocket服務)处面;
- 為發(fā)布者添加一個緩存列表,用于存放回調函數以便通知訂閱者(具體頁面)菩掏;
- 發(fā)布者對外提供消息訂閱/退訂方法魂角,實質就是改變緩存列表的內容;
- 發(fā)布消息時智绸,發(fā)布者會遍歷這個緩存列表野揪,一次觸發(fā)里面存放的訂閱者回調函數访忿;
實踐
首先,把BasciWebsocket.js
變身為消息發(fā)布者:
export default class BasicWebsocket{
constructor() {
if(typeof BasicWebsocket.instance === 'object') {
return BasicWebsocket.instance;
}
//緩存列表
this.listeners = {};
this.init();
BasicWebsocket.instance = this;
}
//消息訂閱
addListener = (key,func)=>{
this.listeners[key] = func;
};
//取消訂閱
removeListener = (key)=>{
delete this.listeners[key];
};
init = ()=>{
//獲取websocket對象,全局唯一
let websocket = this.getWebsocket();
let that = this;
//監(jiān)聽websocket消息
websocket.onmessage = function (event) {
try{
let {data} = event
let _data = JSON.parse(data);
//遍歷緩存列表, 通知消息訂閱者
let keys = Object.keys(that.listeners);
for(let i = 0,j = keys.length; i < j; i++){
let func = that.listeners[keys[i]];
func(_data);
}
}catch(e){
}
};
};
//其他代碼
}
然后斯稳,在發(fā)貨管理頁加載時海铆,可以進行發(fā)貨消息的訂閱;發(fā)貨管理頁卸載時挣惰,進行消息退訂卧斟。其他頁面如此類推。
class Distribution extends Component{
// 構造
constructor(props) {
super(props);
// 初始狀態(tài)
this.state = {
newCount:0
};
this.websocket = new BasicWebsocket();
}
//組件加載完畢
componentDidMount() {
let that = this;
//消息訂閱
this.websocket.addListener("distribution",(d)=>{
if(d.type == 101){
that.showNotification(); //提示“您有新的送貨請求憎茂,請及時處理”
//在頁面展示新的發(fā)貨請求數量
let {count} = d.data;
let count = that.state.newCount + count;
that.setState({
newCount:count
});
}
});
}
//組件即將卸載
componentWillUnmount() {
//消息退訂
this.websocket.removeListener("distribution");
}
}
至此珍语,通過觀察者模式實現了基礎websocket服務和具體頁面邏輯的解耦,代碼可維護性得到提高竖幔。