觀察者模式的項目實踐

業務場景

項目是一個單頁面web應用,有一個基礎的websocket服務,用於和服務器通訊。剛開始主要有兩個做用:javascript

  1. 帳號防重複登錄。當帳號在另外的地方登錄時,websocket收到服務器消息,網站即時彈窗提示用戶「當前帳號在另一個地方登錄」,而後網站清除登陸信息並跳轉到登陸頁面。java

  2. 當打開送貨管理界面時,如有新的送貨請求,websocket收到服務器消息,即時提示用戶「您有新的送貨請求,請及時處理」。若沒有打開送貨管理界面,不提示。web

起初是這樣的實現的:緩存

//BasciWebsocket.js
 websocket.onmessage = function (event) {
   try{
        let {data} = event;
        let _data = JSON.parse(data);
        switch(_data.type){
          case 100:
            Modal.warning({
              title: '帳號登陸提醒',
              content: "您的帳號已經在另外的地方進行登錄,請確認是否您本人操做。若有異常,請聯繫公司系統管理員。",
              okText: '知道了',
              onOk: ()=> {
                tool.clearUserCookie();
                window.location = "/login";
              }
            });
            break;
          case 101:
            let isPageShow = tool.ifDeliveryPageShow();
            if(isPageShow){
               //提示「您有新的送貨請求,請及時處理」
            }
            break;
        }
      }catch(e){

      }
 };

看起來並無什麼問題。可是,隨着項目愈來愈複雜,websocket負責通知的消息也愈來愈豐富。接收到消息後,每每還要改變具體的頁面展示,跟具體的頁面的關聯性很是大。好比,用戶停留在訂單詳情頁時,若是收到新的訂單反饋,那麼訂單詳情頁的評論列表區域就要動畫出現新的反饋信息。服務器

這個時候,你會發現,把這類跟具體頁面強關聯的邏輯寫在基礎的BasciWebsocket.js文件裏不科學,BasciWebsocket.js也變得很臃腫,維護起來有些費勁。問題就在於基礎的websocket服務與每一個頁面的消息處理邏輯不夠解耦。websocket

不難看出,在這個業務場景裏,websocket服務其實就是一個消息發佈者,而這些具體頁面則是消息訂閱者。很天然地,我想到了一根救命稻草——經典的發佈-訂閱模式(又叫觀察者模式)。socket

觀察者模式

觀察者模式定義對象間的一種一對多的依賴關係,當一個對象的狀態發生改變時,全部依賴它的對象都將獲得通知。函數

如何實現觀察者模式?動畫

  1. 指定好誰充當發佈者(websocket服務);網站

  2. 爲發佈者添加一個緩存列表,用於存放回調函數以便通知訂閱者(具體頁面);

  3. 發佈者對外提供消息訂閱/退訂方法,實質就是改變緩存列表的內容;

  4. 發佈消息時,發佈者會遍歷這個緩存列表,一次觸發裏面存放的訂閱者回調函數;

實踐

首先,把BasciWebsocket.js變身爲消息發佈者:

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;
    //監聽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){

      }
    };
  };
  
  //其餘代碼
}

而後,在發貨管理頁加載時,能夠進行發貨消息的訂閱;發貨管理頁卸載時,進行消息退訂。其餘頁面如此類推。

class Distribution extends Component{
  // 構造
  constructor(props) {
    super(props);
    // 初始狀態
    this.state = {
      newCount:0
    };
    this.websocket = new BasicWebsocket();
  }

  //組件加載完畢
  componentDidMount() {
    let that = this; 
    //消息訂閱
    this.websocket.addListener("distribution",(d)=>{
      if(d.type == 101){
        that.showNotification(); //提示「您有新的送貨請求,請及時處理」
        //在頁面展現新的發貨請求數量
        let {count} = d.data;
        let count = that.state.newCount + count;
        that.setState({
          newCount:count
        });
      }
    });
  }

  //組件即將卸載
  componentWillUnmount() {
    //消息退訂
    this.websocket.removeListener("distribution");
  }
}

至此,經過觀察者模式實現了基礎websocket服務和具體頁面邏輯的解耦,代碼可維護性獲得提升。

相關文章
相關標籤/搜索