Mediator Pattern 是一個非常貼近現實生活應用的一個設計模式,小從傳訊息跟朋友聊天,大到聯合國協調國際衝突,只要有中間人、有協調者的地方就有 Mediator Pattern。那麼,我們就先從平常跟朋友們在 LINE 群組裡聊天的方式開始談起吧!不管我們是傳照片、傳貼圖、傳達心意(疑?),這些訊息都會先被送到 LINE 的 server 去做處理,然後 LINE 再將它們傳給群組裡的其他人。也就是說,在群組內的人都是透過 LINE 來互相溝通,因此 LINE 就是大家的中間人,我們只要按下發送按鈕把訊息傳給 LINE,LINE 自然就會想辦法把訊息送達。
在物件導向設計的原則中,我們會儘量想辦法把業務邏輯拆分成一個個獨立、依賴性相對鬆散的元件,以便日後有需求要修改、擴充或替換邏輯時,能夠比較無痛地進行。但是這麼一來,如果要完成一個特定的邏輯,也就需要多個元件之間相互合作來達成,一定程度上地增加了元件間溝通的複雜性。或許這時候 Mediator Pattern 就可以派上用場了。
先來看看 Mediator Pattern 的定義吧:
定義一個 Mediator 物件用來封裝一組物件的互動方式。Mediator 藉由避免物件間相互直接的引用,從而降低它們之間的耦合程度,並且可以讓我們獨立地改變這些物件間的互動方式。
或許我們接著看下面這張概念圖會更清楚。原本物件們都是自己直接跟其它物件互動,不過這樣做的話互相依賴的程度太高,我們希望大家可以不要綁太緊,減少日後修改上的麻煩。所以,我們把所有的物件互動都集中封裝在 Mediator 中,其它元件就直接呼叫 Mediator 的方法來完成特定的任務。這樣,不管我們日後是想要改 Mediator 或是改相關聯的物件,這些變化都有效地被限制在一個範圍內。
讓我們再來看看 Mediator Pattern 的 UML 吧:
下面我們就來依照 Mediator Pattern 的想法,來寫個小小的聊天室程式吧!這個聊天室範例非常單純,我們想要讓每個聊天室的使用者 ChatRoomUser 都不用管訊息傳遞的細節,只要直接把訊息都交給 ChatRoomMediator 來處理就好了。這邊我會使用 Kotlin 語言來示例。
// 定義了各種public方法的Mediator介面
interface ChatRoomMediator {
fun addUser(user: ChatRoomUser)
fun sendMessage(user: ChatRoomUser, msg: String)
}
// Mediator的實作
class ChatRoomMediatorImpl : ChatRoomMediator {
private val userList = mutableListOf<ChatRoomUser>()
override fun addUser(user: ChatRoomUser) {
userList.add(user)
}
override fun sendMessage(user: ChatRoomUser, msg: String) {
// 把user發送的訊息傳給聊天室中的其他人
userList.filter { it.name != user.name }
.forEach { it.receive(msg) }
}
}
// Colleague抽象類別,它會持有Mediator的reference
abstract class ChatRoomUser(
val name: String,
val mediator: ChatRoomMediator
) {
abstract fun send(msg: String)
abstract fun receive(msg: String)
}
// Colleague類別的實作
class ChatRoomUserImpl(
name: String,
mediator: ChatRoomMediator
) : ChatRoomUser(name, mediator) {
override fun send(msg: String) {
// 呼叫Mediator來幫它送訊息給別人
mediator.sendMessage(this, msg)
}
override fun receive(msg: String) {
println("$name received: $msg")
}
}
fun main(args: Array<String>) {
val mediator = ChatRoomMediatorImpl()
// 把Mediator的reference傳給各個Colleague
val bob = ChatRoomUserImpl("Bob", mediator)
val alice = ChatRoomUserImpl("Alice", mediator)
val tom = ChatRoomUserImpl("Tom", mediator)
// 讓Mediator跟各個ConcreteColleague產生關聯
mediator.addUser(bob)
mediator.addUser(alice)
mediator.addUser(tom)
// Yay!只要照常使用Colleague,Mediator將會幫我們把一切都處理好
bob.send("How are you?")
alice.send("I'm fine.")
tom.send("Bye")
}
Mediator Pattern 很常被應用在 UI 的開發上,特別像是表單一類的程式。舉個例子,讓我們想像一下一個註冊新帳號的表單,使用者一定要輸入一個不重複的帳號名稱、不為空白的密碼,這樣確認送出的按鈕才會變成可以按的狀態,不然該按鈕就仍會處於不可按的狀態。每個表單上的元件其實都是 Mediator Pattern 中的 Colleague 類別,它們會將使用者對自身的所有操作事件都回報給 Mediator 類別來做整合,大概就像是:
class FormMediator {
...
// 一有新的表單操作事件,Colleague會呼叫這個方法讓Mediator處理
fun handleChange() {
...
// 如果帳號有效,並且密碼不為空白,則啟用確認送出的按鈕
if (AccountValidator.validate(accountField.getContent())
&& passwordField.isNotEmpty()) {
enableBtn(submitBtn)
}
...
}
}
如果這個表單內容更加複雜、有更多的表單元件需要處理,那麼將這些元件的控制集中統一在一個地方處理(像是本例中的 handleChange()
)的做法,將可以更有效地減少程式中其它地方的表單控制邏輯。
Mediator Pattern 在 Android framework 中最經典的應用或許就是 keyguard 了,它是系統中負責螢幕上鎖、解鎖、處理驗證碼相關功能的模組。讓我們來看看 Google 是怎麼應用 Mediator Pattern 在 keyguard 上的吧!
首先,keyguard 的 Mediator 類別是 /frameworks/base/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java:
它的程式碼註解開頭是這樣寫的:
Mediates requests related to the keyguard. This includes queries about the state of the keyguard, power management events that effect whether the keyguard should be shown or reset, callbacks to the phone window manager to notify it of when the keyguard is showing, and events from the keyguard view itself stating that the keyguard was succesfully unlocked.
所以我們大概可以知道,這個 Mediator 要負責的工作會蠻多的,它將會需要管理各種 keyguard 相關的事件和狀態變化,以及協調各種系統元件的運作。
public class KeyguardViewMediator extends SystemUI {
// 內部管理的 Colleague 們
private AlarmManager mAlarmManager;
private AudioManager mAudioManager;
private StatusBarManager mStatusBarManager;
private PowerManager mPM;
private TrustManager mTrustManager;
private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
private KeyguardUpdateMonitor mUpdateMonitor;
...
public void onStartedGoingToSleep(int why) {
...
}
public void onFinishedGoingToSleep(int why, boolean cameraGestureTriggered) {
...
}
...
}
除了 KeyguardViewMediator
內部所管理的 Colleague 們外,KeyguardViewMediator
也提供了許多 public 方法,供其它外部的 Colleague 元件們呼叫,像是:KeyguardService、FingerprintUnlockController 等等,它們藉由呼叫這些 public 方法將 keyguard 相關事件交給 KeyguardViewMediator
處理,KeyguardViewMediator
內部再統一協調各個系統元件來完成工作。
可以把原本 Colleague 類別間錯綜複雜的多對多關係,轉換為以 Mediator 類別為主的一對多關係,這就可以有效地減少 Colleague 間互相依賴的程度,從而讓我們可以更容易地改動個別的 Colleague 類別,而不影響到其它的 Colleague 們。
可以想像,因為 Colleague 互動邏輯都被封裝在 Mediator 類別中,所以一旦當要處理的邏輯非常複雜時,Mediator 可能就會變得非常肥大,變成了所謂的 God Class,反倒搞得不好維護了。
這兩個模式看起來很像,同樣都是要使用一個特殊的封裝類別 (Mediator 類別或 Facade 類別) 來隔離依賴。不過 Facade 主要的目的是減少子系統暴露對外的介面方法,功能都已經存在在系統中了,沒有要新增更多的功能,只是想要減少要公開出去的東西,並且只能被子系統外的元件單向地呼叫。但 Mediator 呢,則是增加功能來集中管理系統中 Colleague 類別們的互動行為,減少 Colleague 間的耦合,並且 Mediator 可以操作 Colleague,Colleague 也可以操作 Mediator。
每當我們發現系統中存在依賴關係錯綜複雜的情況的時候,或許可以考慮引進 Mediator Pattern 來整理這些依賴,把多對多的依賴關係轉化為以 Mediator 為中心的一對多依賴關係,以此降低元件互相依賴的程度。但是,如果系統其實很單純,只有幾個類別在協作(就像是前面的聊天室範例程式),其實沒有必要使用 Mediator Pattern,引進了 Mediator 反倒是徒增了系統的複雜性。
作者:Joshua