中介者模式

語言: CN / TW / HK

theme: condensed-night-purple highlight: atom-one-dark


基本概念

中介者模式 (Mediator Pattern)又稱調停模式,使得各物件不用顯式地相互引用,將物件與物件之間緊密的耦合關係變得鬆散,從而可以獨立地改變他們。核心是多個物件之間複雜互動的封裝

根據最少知識原則,一個物件應該儘量少地瞭解其他物件。如果物件之間耦合性太高,改動一個物件則會影響到很多其他物件,可維護性差。複雜的系統,物件之間的耦合關係會得更加複雜,中介者模式就是為了解決這個問題而誕生的。

現實生活中的例子

說到中介者模式很容易想到媒人的例子吧,我在前面的 代理模式 中也用到了媒人這個例子,這時中介者就相當於同事物件間的代理。所以這時就可以引入代理模式的概念,對同事物件相互訪問的時候,起到訪問控制、功能擴充套件等等功能。

再舉個飛機與塔臺的例子:

飛行器駕駛員們在靠近或離開空中管制區域時不會直接相互交流。但他們會與飛機跑道附近,塔臺中的空管員通話。如果沒有空管員,駕駛員就需要留意機場附近的所有飛機,並與數十位飛行員組成的委員會討論降落順序。那恐怕會讓飛機墜毀的統計資料一飛沖天吧。塔臺無需管制飛行全程,只需在航站區加強管控即可,因為該區域的決策參與者數量對於飛行員來說實在太多了。

應用場景

中介者模式適用多個物件間的關係確實已經緊密耦合,且導致擴充套件、維護產生了困難的場景,也就是當多個物件之間的引用關係變成了網狀結構的時候,此時可以考慮使用引入中介者來把網狀結構轉化為星型結構。如下圖所示:

中介者模式.png

但是,如果物件之間的關係耦合並不緊密,或者之間的關係本就一目瞭然,那麼引入中介者模式就是多此一舉、畫蛇添足。

實際上,我們通常使用的 MVC/MVVM 框架,就含有中介者模式的思想,Controller/ViewModel 層作為中介者協調 View/Model 進行工作,減少 View/Model 之間的直接耦合依賴,從而做到檢視層和資料層的最大分離。

優缺點

優點:

  1. 鬆散耦合,降低了同事物件(上面圖中的 ABCDE就是同事物件)之間的相互依賴和耦合,不會像之前那樣牽一髮動全身;
  2. 將同事物件間的一對多關聯轉變為一對一的關聯,符合最少知識原則,提高系統的靈活性,使得系統易於維護和擴充套件;
  3. 中介者在同事物件間起到了控制和協調的作用,因此可以結合代理模式那樣,進行同事物件間的訪問控制、功能擴充套件
  4. 因為同事物件間不需要相互引用,因此也可以簡化同事物件的設計和實現

缺點:

  1. 邏輯過度集中化,當同事物件太多時,中介者的職責將很重,邏輯變得複雜而龐大,以至於難以維護。

當出現中介者可維護性變差的情況時,考慮是否在系統設計上不合理,從而簡化系統設計,優化並重構,避免中介者出現職責過重的情況。

實現

要實現中介者模式需要先知道兩個概念,這兩個概念在前面其實也有提到過:

  1. Colleague: 同事物件,只知道中介者而不知道其他同事物件,通過中介者來與其他同事物件通訊;
  2. Mediator: 中介者,負責與各同事物件的通訊;

```js / 男方 / const ZhangXiaoShuai = { name: '張小帥', family: '張小帥家', info: { age: 25, height: 171, salary: 5000 }, target: { age: [23, 27] } }

/ 男方家長 / const ZhangXiaoShuaiParent = { name: '張小帥家長', family: '張小帥家', info: null, target: { height: [160, 167] } }

/ 女方 / const LiXiaoMei = { name: '李小美', family: '李小美家', info: { age: 23, height: 160 }, target: { age: [25, 27] } }

/ 女方家長 / const LiXiaoMeiParent = { name: '李小美家長', family: '李小美家', info: null, target: { salary: [10000, 20000] } }

/ 媒人 / const MatchMaker = { matchBook: {}, // 媒人的花名冊

/ 註冊各方 / registPersons(...personList) { personList.forEach(person => { if (this.matchBook[person.family]) { this.matchBook[person.family].push(person) } else this.matchBook[person.family] = [person] }) },

/ 檢查對方家庭的孩子物件是否滿足要求 / checkAllPurpose() { Object.keys(this.matchBook) // 遍歷名冊中所有家庭 .forEach((familyName, idx, matchList) => matchList .filter(match => match !== familyName) // 對於其中一個家庭,過濾出名冊中其他的家庭 .forEach(enemyFamily => this.matchBook[enemyFamily] // 遍歷該家庭中註冊到名冊上的所有成員 .forEach(enemy => this.matchBook[familyName] .forEach(person => // 逐項比較自己的條件和他們的要求 enemy.info && this.checkPurpose(person, enemy) ) )) ) },

/ 檢查對方是否滿足自己的要求,併發資訊 / checkPurpose(person, enemy) { const result = Object.keys(person.target) // 是否滿足自己的要求 .every(key => { const [low, high] = person.target[key] return low <= enemy.info[key] && enemy.info[key] <= high }) this.receiveResult(result, person, enemy) // 通知對方 },

/ 通知對方資訊 / receiveResult(result, person, enemy) { result ? console.log(${person.name} 覺得合適~ \t(${enemy.name} 已經滿足要求)) : console.log(${person.name} 覺得不合適! \t(${enemy.name} 不能滿足要求!)) } }

/ 註冊 / MatchMaker.registPersons(ZhangXiaoShuai, ZhangXiaoShuaiParent, LiXiaoMei, LiXiaoMeiParent)

MatchMaker.checkAllPurpose()

// 輸出: 小帥 覺得合適~ (李小美 已經滿足要求) // 輸出: 張小帥家長 覺得合適~ (李小美 已經滿足要求) // 輸出: 李小美 覺得合適~ (張小帥 已經滿足要求) // 輸出: 李小美家長 覺得不合適! (張小帥 不能滿足要求!) ```

可以看到,除了媒人之外,其他各個角色都是獨立的,相互不知道對方的存在,物件間關係被解耦,我們甚至可以方便地新增新的物件。

「其他文章」