天天看點

《JavaScript設計模式》——9.6 Mediator(中介者)模式

本節書摘來自異步社群《javascript設計模式》一書中的第9章,第9.6節, 作者: 【美】addy osmani 譯者: 徐濤 更多章節内容可以通路雲栖社群“異步社群”公衆号檢視。

在字典裡,中介者是指“協助談判和解決沖突的中立方”1。在本書設計模式裡,中介者是一種行為設計模式,它允許我們公開一個統一的接口,系統的不同部分可以通過該接口進行通信。

如果一個系統的各個元件之間看起來有太多的直接關系,也許是時候需要一個中心控制點了,以便各個元件可以通過這個中心控制點進行通信。mediator模式促進松散耦合的方式是:確定元件的互動是通過這個中心點來處理的,而不是通過顯式地引用彼此。這種模式可以幫助我們解耦系統并提高元件的可重用性。

現實世界的一個例子是典型的機場交通控制系統。機場控制塔(中介者)處理飛機的起飛和降落,因為所有通信(監聽到或發出的通知)都是從飛機到控制塔,而不是飛機和飛機直接互相通信的。中央控制系統是該系統成功的關鍵,而這才是中介者在軟體設計中所擔任的真正角色(圖9-5)。

《JavaScript設計模式》——9.6 Mediator(中介者)模式

就實作而言,mediator模式本質上是observer模式的共享目标。它假設該系統中對象或子產品之間的訂閱和釋出關系被犧牲掉了,進而維護中心聯絡點。

它也可能被認為是額外的或者是用于應用程式間的通知,如不同子系統之間的通信,這些子系統本身就很複雜,且可能希望通過釋出/訂閱關系實作内部元件間的解耦。

另一個例子是 dom 事件冒泡和事件委托。如果系統中所有的訂閱針對的是文檔document而不是單個node節點,則這個文檔會有效地充當中介者。更進階别(level)的對象承擔了向訂閱者通知有關互動事件的責任,而不是綁定到單個節點的事件。

**

9.6.1 基本實作**

可以在下面找到mediator模式的簡單實作,暴露了publish()和subscribe()方法來使用:

9.6.2 進階實作

如果你對更進階的代碼實作感興趣,深入下去可以浏覽到 jack lawson 的優秀mediator.js(//thejacklawson.com/mediator.js/)的簡潔版。除了其他改進以外,這個版本支援 topic 命名空間、訂閱者删除和用于中介者的更強大的釋出/訂閱(publish/subscribe)系統。但如果你想跳過這些内容,則可以直接進入下一個示例繼續閱讀。2

首先,讓我們來實作訂閱者的概念,可以考慮一個mediator的topic注冊執行個體。

通過生成對象執行個體,之後我們可以很容易地更新訂閱者,而不需要登出并重新注冊它們。訂閱者可以寫成構造函數,該函數接受三個參數:一個可被調用的函數fn、一個options對象和一個context(上下文)。

mediator中的topic持有了一組回調函數和子topic清單,一旦mediator.publish方法在mediator執行個體上被調用時,這些回調函數就會被觸發。它還包含用于操作資料清單的方法。

// 模拟topic

// javascript允許我們使用function對象作為原型的結合與新對象和構造函數一起調用

// 定義topic的prototype原型,包括添加訂閱者和擷取訂閱者的方式

我們的topic執行個體作為一個參數傳遞給mediator回調。然後可以stoppropagation()的簡便方法來調用進一步的回調傳播:

當給定guid辨別符時,我們也可以很容易擷取現有的訂閱者:

接下來,如果需要它們,我們可以提供簡單方法來添加新topic、檢查現有topic或者擷取topic:

如果不再需要訂閱者,我們可以顯式地删除它們。以下代碼将通過它的子主題遞歸删除一位訂閱者:

接下來,我們将通過子topic遞歸向訂閱者釋出(publish)任意參數。

這裡暴露了我們将主要與之互動的mediator執行個體。在這裡,完成了事件在topic上的注冊和移除。

對于更進階的使用場景,我們可以讓mediator支援用于inbox:messages:new:read等主題topic的命名空間。在接下來的示例中,gettopic根據命名空間傳回相應的主題執行個體。

在本小節中,我們定義了mediator.subscribe方法,它接受一個topic命名空間、一個可執行的fn函數、options,以及調用該函數的context上下文。如果topic不存在,則建立一個。

繼續下去,我們可以進一步定義用于通路特定訂閱者或将訂閱者從topic中遞歸删除的實用程式。

// 通過給定的訂閱者id/命名函數和topic命名空間傳回一個訂閱者

// 通過給定的訂閱者id或命名函數,從給定的topic命名空間遞歸删除訂閱者

主要的publish方法允許我們向所選擇的topic命名空間任意釋出資料。

topic向下遞歸調用。例如,一個發往inbox:messages的文章将被發至inbox:messages: new和inbox:messages:new:read。如下所示:

最後,我們可以很容易地将mediator作為一個對象附加到root上:

9.6.3 示例

通過使用上述的任一種實作(簡單的和進階的實作),我們可以建立一個簡單的聊天記錄系統,如下所示。

如下是html代碼:

《JavaScript設計模式》——9.6 Mediator(中介者)模式

如下是javascript代碼:

// 将資料釋出到newmessage主題上

// 将新消息附加到聊天結果記錄上

// 記錄消息日志

// 通過mediator訂閱新送出的newmessage主題

// 如下代碼僅在進階代碼實作上可以使用

9.6.4 優點和缺點

mediator模式的最大好處是:它能夠将系統中對象或元件之間所需的通信管道從多對多減少到多對一。由于現在的解耦程度較高,添加新釋出者和訂閱者相對也容易多了。

或許使用這種模式最大的缺點是:它會引入單一故障點。将mediator放置于子產品之間可以導緻性能下降,因為它們總是間接地進行通信。由于松耦合的性質,很難通過僅關注廣播來确定一個系統如何作出反應。

也就是說,自我提醒解耦的系統有很多其他的優點:如果子產品之間直接互相通信,子產品的改變(如另一個子產品抛出一個異常)容易讓應用程式的其餘部分産生多米諾效應。這個問題對解耦的系統來說就不是個大問題。

最後,緊密耦合會引起各種各樣的問題,這隻是另一個替代方案,但如果正确地實作,它也能很好地工作。

9.6.5 中介者(mediator)與觀察者(observer)

開發人員通常想知道mediator模式和observer模式之間的差異是什麼。無可否認地,它們之間有一些重疊,讓我們重新參考“四人組”作出的解釋:

在observer模式中,不存在封裝限制的單一對象。observer和subject(目标)必須合作才能維持限制。communication(通信)模式由觀察者和目标互連的方式所決定:單一目标通常有很多觀察者,有時一個目标的觀察者是另一個觀察者的目标。

mediator和observer都能夠促進松耦合;然而,mediator模式通過限制對象嚴格通過mediator進行通信來實作這一目的。observer模式建立觀察者對象,觀察者對象向訂閱它們的對象釋出其感興趣的事件。

9.6.6 中介者(mediator)與外觀(facade)

我們将簡單提一下facade模式,但出于引用目的,一些開發人員可能也想知道mediator和facade模式之間是否有相似點。它們都能夠抽象出現有子產品的功能,但是也有一些細微的差别。

mediator子產品在它被子產品顯式引用的地方彙集這些子產品之間的通信。從某種意義上說,這是多方向的。另一方面,facade模式僅是為子產品或系統定義了一個較簡單的接口,而沒有添加任何額外的功能。系統中的其他子產品不會直接關聯外觀,是以可以被視為單向的。

繼續閱讀