中介者設計模式——業務實踐

定義:中介者設計模式是經過中介對象封裝一系列對象之間的交互,使對象之間再也不相互引用,下降他們之間的耦合。javascript

中介者設計模式和觀察者設計模式同樣,都是經過消息的收發機制實現的,在觀察者模式中,一個對象既能夠是消息的發送者也是消息的接收者,對象之間信息交流依託於消息系統實現解耦。而中介者模式中消息發送送方只有一個,就是中介對象,並且中介對象不能訂閱消息,只有那些活躍對象(訂閱者)纔可訂閱中介者的消息,簡單的理解能夠看做是將消息系統封裝在中介者對象內部,因此中介者對象只能是消息的發送者。前端

實現原理java

image.png | left | 728x414

建立中介者對象(調度中心)

廢話很少說直接上代碼;git

// eventeimtter.js

// 建立中介者對象(調度中心)
class EventEimtter {
  constructor() {
    // 建立消息對象
    this.$event = {};
  }
  /** * 檢測消息對象是否存在,不存在則初始化該消息 * @param {*} event */
  checkEvent(event) {
    if (!this.$event) {
      this.$event = {};
    }

    if (!this.$event[event]) {
      this.$event[event] = [];
    }
  }
  /** * 訂閱消息 * @param {*} type 消息類型 * @param {*} action * @param {*} context 消息做用域上下文 */
  on(type, action, context = null) {
    this.checkEvent(type);
    this.$event[type].push(action.bind(context));
    return this;
  }
  /** * 發送消息 * @param {*} type * @param {...any} args */
  emit(type, ...args) {
    if (!this.$event[type]) {
      this.$event[type] = [];
    }
    this.$event[type].forEach(func => {
      func(...args);
    });
    return this;
  }
  /** * 僅能發送一次 * @param {*} type * @param {*} action * @param {*} scope 做用域 */
  once(type, action, scope = null) {
    this.checkEvent(type);
    const newfn = (...args) => {
      this.off(type, action);
      action.call(scope, ...args);
    };
    this.on(type, newfn);
    return this;
  }
  /** * 移除已經訂閱的消息 * @param {*} type * @param {*} action */
  off(type, action) {
    const $event = this.$event[type];
    if ($event) {
      for (let i in $event) {
        if ($event[i] === action) {
          $event.splice(i, 1);
          break;
        }
      }

      if (!$event.length) {
        delete this.$event[type];
      }
    }

    return this;
  }
  /** * 移除某個的類型消息 * @param {*} type */
  removeListener(type) {
    delete this.$event[type];
    return this;
  }
  /** * 移除全部訂閱消息 */
  removeAllListener() {
    this.$event = null;
    return this;
  }
  /** * 獲取全部的消息類型 */
  getEvent() {
    return this.$event;
  }
}


export default EventEimtter;
複製代碼

小試牛刀,能否一用

在這裏,我只須要訂閱兩個消息,而後讓中介者發佈;看看是否可以發佈成功。github

//單元測試
import EventEimtter from './eventeimtter';

const event = new EventEimtter();

// 訂閱 demo 消息,執行回調函數 ———— 輸出 first
event.on('demo', () => {
  console.log('first');
});
// 訂閱 demo 消息,執行回調函數 ———— 輸出 second
event.on('demo', () => {
  console.log('second');
})

// 發佈 demo 消息
event.emit('demo')
// first
// second
複製代碼

業務價值的產生,實際開發中的實踐

先說痛點,在實際的項目開發中一個頁面 js 可能有十幾個 class 類;你所見到的代碼會是這樣的。ajax

image.png | left | 827x1279

以上代碼中,能夠看出一個 React 組件,徹底不見 React 周期函數,類函數過多 ,render 函數過於龐大;監聽的方法也不少,閱讀,維護,迭代成功太高。這段代碼不論是對於開發者自己仍是維護者,都不友好;迫切須要代碼拆分,且實現結構層次清晰。後端

然而實際開發中,業務變動、迭代過快,有的業務自己複雜度極高,一個項目經手人也不少。若是代碼不整潔,後來人就很難看懂,人們每每會對難以看懂的代碼失去耐心,不肯意進一步瞭解。若是不能進一步瞭解一部分代碼,也就難以改進它,這樣的後果可能有兩點:設計模式

  • 重構,代碼被拋棄
  • 直接複製這段代碼在別的地方使用

下面是我站在前端的角度去思考業務:瀏覽器

image.png | left | 827x664

  1. 業務數據:負責獲取業務數據
  2. 業務邏輯:實現產品所定義的規則
  3. 邏輯數據:經過一系列規則所產出的邏輯數據
  4. 視圖數據:經過邏輯數據轉換成視圖數據(不將邏輯和視圖直接綁定)
  5. 視圖展現:經過視圖數據,直接驅動視圖層展現對應視圖
  6. 視圖功能:經過視圖展現組裝成的需求功能

在簡單的業務需求中,可能我拿到的後端數據,就直接能夠去渲染視圖層,而後就完善功能。從開發的成本和複雜度上考量上,是不值得去作業務拆分。因此,在複雜的業務需求中以及兼顧拆分和維護中,這種業務方法論就能夠大展手腳了。如下,我就拿開頭的例子,詳細解析圍繞業務的6大部分的設計。函數

項目實踐

我始終堅信技術的價值是在業務中產生的,技術自己是沒有價值的,技術的價值取決因而否能在項目中落地以及解決業務的痛點。做爲中介者模式在項目中的落地,先舉一個小栗子!

image.png | left | 827x265

需求列表以下
  • 一個分頁表格, 分別有網點名稱、網點地址、聯繫電話、操做欄四列。
  • 每一行操做欄有三個按鈕,分別是 桌位管理、頁面裝修、功能設置

通常要求:使用 zent 分頁表格 Table 組件,配置好 columns ,操做欄定製渲染;更加簡易的拓展以及敏捷的操做,固然維護和開發的成本也須要考慮的。

使用 zent table 組件開發,受益於 React 數據驅動的思想,columns 是以 props 傳入;columns 中的定製渲染,可能須要涉及到父子組件之間的通訊。

在正常的開發中,咱們能夠這麼作。

const event = new EventEimtter();


const columns = [
  ...,
  {
    title: '操做',
    bodyRender: (rowData) => {
      return (
        <div> <Button onClick={() => { event.emit('page-decoration', rowData) }}> 桌位裝修 </Button> <Button onClick={() => { event.emit('desk-manage', rowData) }}> 桌位裝修 </Button> <Button onClick={() => { event.emit('action-setting', rowData) }}> 桌位裝修 </Button> </div>
      );
    }
  },
  ....
]

// Action 消息處理函數實體類,業務邏輯源碼
class Action {
  handlerPageDecoration() {
    ...
  }
  handlerDeskManage() {
    ...
  }

  handlerActionSetting() {
    ...
  }
}

const action = new Action()


class Demo extends Component {
  componentWillMount() {
    // 訂閱消息
    event.on('page-decoration', action.handlerPageDecoration, this)
    event.on('desk-manage', action.handlerDeskManage, this)
    event.on('action-setting', action.handlerActionSetting, this)
  }

  render() {
    return (
      <Table columns={columns} ...props/>
    );
  }

  componentWillUnmount() {
    // 當該組件銷燬時,取消因此監聽事件;不然內存會炸掉
    event.removeAllListener();
  }
}
複製代碼
生命週期的使用時機

image.png | left | 827x841

React 生命週期

  • constructor:儘可能簡潔,只作最基本的 state 初始化
  • willMount: 一些內部使用變量的初始化
  • render: 觸發很是頻繁,儘可能只作渲染相關的事情
  • didMount: 一些不影響初始化的操做應在這裏完成,好比根據瀏覽器不一樣進行操做,ajax獲取數據,監聽 document 事件等(server render)。
  • willUnmount:銷燬操做,銷燬計時器、銷燬本身的事件監聽等
  • willReceiveProps: 當有 props 作 state 時,監聽 props 的變化去改變 state,在這個生命週期裏 setState 不會觸發兩次渲染
  • shouldComponentUpdate:手動判斷組件是否應該更新,避免由於頁面更新作成的無謂更新,組件的重點優化之一。
  • willUpdate:在 state 變化後若是須要修改一些變量,能夠在這裏執行
  • didUpdate: 與 didMount 相似,進行一些不影響到 render 的操做, update 相關的生命週期裏最好不要作 setState 操做,不然容易形成死循環。
在 React 生命週期中,實踐業務數據轉換

image.png | left | 826x667

業務數據的來源:

  • ReactCompoent 在 willMount 時,初始化的 state、props中獲取
  • didMount 時 Ajax 獲取的數據 業務邏輯(業務規則):
  • 處理業務規則的源碼,根據不一樣的規則,對業務數據進行處理
  • 產生邏輯數據
  • 須要在 constructor  或者 willMount  中完成業務邏輯的訂閱 邏輯數據:
  • 使用業務邏輯處理產生,同步到視圖數據 試圖數據:
  • 同步邏輯數據的,中間可加 hook 視圖展現:
  • 根據視圖數據單項 render

深耕業務開發與設計

image.png | left | 827x410

總結

同觀察者模式同樣,中介者模式的主要業務也是經過模塊間或者對象間的複雜通訊,來解決模塊間或對象的耦合。對於中介者對象的本質是分裝多個對象的交互,而且這些對象的交互通常都是中介者內部實現的。

與外觀模式的封裝特性相比,中介者模式對多個對象的交互封裝,且這些對象通常處於同一層面上,而且封裝的交互在中介者內部,而外觀模式封裝的目的是爲了提供更簡單的易用接口,而不會添加其餘功能。

相關文章
相關標籤/搜索