意圖
用一個中介對象來封裝一系列的對象交互。中介者使各對象不需要顯式地相互引用,從而使其耦合松散,而且可以獨立地改變它們之間的交互。
中介者模式的本質:封裝交互。
適用性
- 如果一組對象之間的通信方式比較復雜,導致相互依賴、結構混亂,可以采用中介者模式,把這些對象相互的交互管理起來,各個對象都只需要和中介者交互,從而使得各個對象松散耦合,結構也更清晰易懂。
- 如果一個對象引用很多的對象,并直接跟這些對象交互,導致難以復用該對象??梢圆捎弥薪檎吣J剑堰@個對象跟其它對象的交互封裝到中介者對象里面,這個對象就只需要和中介者對象交互就可以了。
結構
- Mediator(中介者接口)
在里面定義各個同事之間交互需要的方法,可以是公共的通訊方法,比如changed方法,大家都用,也可以是小范圍的交互方法。 - ConcreteMediator(具體中介者類)
它需要了解并維護各個同事對象,并負責具體的協調各同事對象的交互關系。 - Colleague(同事類)
通常實現成為抽象類,主要負責約束同事對象的類型,并實現一些具體同事類之間的公共功能,比如:每個具體同事類都應該知道中介者對象,也就是具體同事類都會持有中介者對象,就可以定義到這個類里面。 - ConcreteColleague(具體同事類)
具體的同事類,實現自己的業務。每一個同事類都知道它的中介者對象。每一個同事對象在需與其他的同事通信的時候,與它的中介者通信。
場景解說
下面我們通過一個場景來讓大家更容易對中介者模式有一個直觀的認識。
場景:
感謝未來屋公司的這群好家伙,Bob擁有一個Java版本的自動屋,這可以讓他的生活變得更便利。當Bob點擊了打盹按鈕,他的鬧鐘就會告訴咖啡壺開始煮咖啡。盡管生活對他來說是如此愜意,但他(以及其他的客戶)總是不斷地提出許多新的要求:周末不要供應咖啡......在洗澡前將噴頭關閉15分鐘......在丟垃圾的日子里將鬧鐘時刻提前......
問題:想要持續地追蹤每個對象的每個規則,以及眾多對象之間彼此錯綜復雜的關系,實在不容易。
解決方案:使用中介者模式
仔細分析上面的問題,根本原因就在于多個對象需要相互交互,從而導致對象之間緊密耦合,這就不利于對象的修改和維護。
在這個系統中加入一個中介者,一切都變得簡單了。
- 每個對象都會在自己的狀態改變時,告訴中介者
- 每個對象都會對中介者所發出的請求作出回應。
在沒有中介者的情況下,所有的對象都需要認識其他對象......也就是說,對象之間是緊耦合的。有了中介者之后,對象之間徹底解耦。
中介者內包含了整個系統的控制邏輯。當某裝置需要一個新的規則時,或者是一個新的裝置被加入系統內,其所有需要用到的邏輯也都被加進了中介者內。
對中介者模式實現的深入探討
- 忽略抽象的Mediator類
有沒有使用Mediator接口的必要,那就取決于是否會提供多個不同的中介者實現。如果中介者實現只有一個的話,而且預計中也沒有需要擴展的要求,那么就可以不定義Mediator接口,讓各個同事對象直接使用中介者實現對象;如果中介者實現不只一個,或者預計中有擴展的要求,那么就需要定義Mediator接口,讓各個同事對象來面向中介者接口編程,而無需關心具體的中介者實現。 - Colleague ———— Mediator 通信
Colleague 與 Mediator 之間通信有兩種實現方式:
① 第一種實現方式是:使用觀察者模式,將Mediator實現為一個Observer,各Colleague作為Subject,一旦其狀態改變就發送通知給Mediator。Mediator作出的響應是將狀態改變的結果傳播給其他的Colleague。
② 第二種實現方式是:在Mediator中定義一個特殊的通知接口,各Colleague在通信時直接調用該接口。
優缺點
優點
① 松散耦合
中介者模式通過把多個同事對象之間的交互封裝到中介者對象里面,從而使得同事對象之間松散耦合,基本上可以做到互不依賴。這樣一來,同事對象就可以獨立的變化和復用,而不再像以前那樣“牽一發而動全身”了。
② 集中控制交互
多個同事對象的交互,被封裝在中介者對象里面集中管理,使得這些交互行為發生變化的時候,只需要修改中介者對象就可以了,而各個同事類不需要做修改。這使得你將注意力從對象各自本身的行為轉移到它們之間的交互上來,這有助于弄清楚一個系統中的對象是如何交互的。
③ 多對多變成一對多
沒有使用中介者模式的時候,同事對象之間的關系通常是多對多的,引入中介者對象過后,中介者對象和同事對象的關系通常變成了雙向的一對多,這會讓對象的關系更容易理解和實現。
缺點
① 過度集中化
中介者模式的一個潛在缺點是,如果同事對象的交互非常多,而且比較復雜,當這些復雜性全部集中到中介者的時候,會導致中介者對象變得十分的復雜,而且難于管理和維護。
廣義中介者
仔細查看中介者的結構、定義和示例,會發現幾個問題,使得中介者模式在實際使用的時候,變得繁瑣或困難。
Q:是否有必要為同事對象定義一個公共的父類?
大家都知道,Java是單繼承的,為了使用中介者模式,就讓這些同事對象繼承一個父類,這是很不好的;再說了,這個父類目前也沒有什么特別的公共功能,也就是說繼承它也得不到多少好處。
A:在實際開發中,很多相互交互的對象本身是沒有公共父類的,強行加上一個父類,會讓這些對象實現起來特別別扭。
Q:同事類有必要持有中介者對象嗎?
同事類需要知道中介者對象,以便當它們發生改變的時候,能夠通知中介者對象,但是,是否需要作為屬性,并通過構造方法傳入,這么強的依賴關系呢?
A:也可以有簡單的方式去通知中介對象,比如把中介對象做成單例,直接在同事類的方法里面去調用中介者對象。
Q:是否需要中介者接口?
A:在實際開發中,很常見的情況是不需要中介者接口的,而且中介者對象也不需要創建很多個實例,因為中介者是用來封裝和處理同事對象的關系的,它一般是沒有狀態需要維護的,因此中介者通常可以實現成單例。
Q:其四:中介者對象是否需要持有所有的同事?
A:雖說中介者對象需要知道所有的同事類,這樣中介者才能與它們交互。但是是否需要做為屬性這么強烈的依賴關系,而且中介者對象在不同的關系維護上,可能會需要不同的同事對象的實例,因此可以在中介者處理的方法里面去創建、或者獲取、或者從參數傳入需要的同事對象。
Q:中介者對象只是提供一個公共的方法,來接受同事對象的通知嗎?
A:在實際開發中,通常會提供具體的業務通知方法,這樣就不用再去判斷到底是什么對象,具體是什么業務了。
實際開發中我們使用更多的是中介者模式的變種:
只要是實現封裝對象之間的交互功能,就可以應用上中介者模式,而不必過于拘泥于中介者模式本身的結構。標準的中介者模式限制很多,導致能完全按照標準使用中介者模式的地方并不是很多,而且多集中在界面實現上。只要本質不變,稍稍變形一下,簡化一下,或許能更好的使用中介者模式。
相關模式
中介者模式和外觀模式
這兩個模式有相似的地方,也存在很大的不同。
外觀模式多用來封裝一個子系統內部的多個模塊,目的是向子系統外部提供簡單易用的接口,也就是說外觀模式封裝的是子系統外部和子系統內部模塊間的交互;而中介者模式是提供多個平等的同事對象之間交互關系的封裝,一般是用在內部實現上。
另外,外觀模式是實現單向的交互,是從子系統外部來調用子系統內部,不會反著來,而中介者模式實現的是內部多個模塊間多向的交互。
中介者模式和觀察者模式
這兩個模式可以組合使用。
中介者模式可以組合使用觀察者模式,來實現當同事對象發生改變的時候,通知中介對象,讓中介對象去進行與其它相關對象的交互。
參考
《Head First 設計模式》
《設計模式:可復用面向對象軟件的基礎》
《研磨設計模式》