中介者模式(Mediator)
調度、調停
意圖
用一個中介對象(中介者)來封裝一系列的對象交互,中介者使各對象不需要顯式地相互引用,從而使其耦合松散
而且可以獨立地改變它們之間的交互。
中介者模式又稱為調停者模式。
面向對象的程序設計中,我們通常將功能進行分解,按照職責以類為維度進行划分,也就是使用時功能最終將分布在多個對象中
並且我們會盡可能的保持對象功能的單一(單一職責原則)
相對於對象的單一職責來說,任何的系統或者模塊的功能卻並不會單一,往往都是有多個對象交互協作來實現所有的功能
對象之間不可避免的需要建立連接
換句話說
系統(或者某模塊)必然是復雜的(沒有什么系統可以幾個對象就輕松搞定,那樣或許也不能稱之為系統了吧)
功能必然會分布在多個對象中
多個對象協作必然需要聯系,
這
必然導致耦合的產生
如上圖所示,雖然系統對外呈現是一個統一的整體,但是,內部各個模塊之間很可能是緊密的耦合
各個
模塊相互聯系,可能互相持有引用,會出現網狀結構,完全不符合迪米特法則。
如果
對系統進行改動,將會變得困難。
我們以裝修為例
一般裝修公司都會給每一個項目配備一個項目經理(這個項目也就是你家這個單子了,項目經理就是包工頭)
裝修的一般階段分為:前期設計→拆改→水電→瓦工→木工→油漆→安裝→保潔→軟裝
項目經理手上經常同時有幾個工地在同步進行,只要錯的開就好了
因為每個階段都是有先后順序的,你不可能先木工,然后再去拆改;
因為每個階段也都需要一定時間,也意味着這一撥人不可能同時在你家工作
開工后項目經理會進行工作安排
水電工結束了A之后,項目經理會安排他到B,然后安排瓦工到A,然后........
所有的順序都是由項目經理負責調度,水電工可以完全不認識瓦工,他們也完全不需要進行聯系
有事兒找項目經理
如果沒有項目經理,會是什么場景?
那就是人人都是項目經理,人人都需要管自己,還需要管別人
也就是每個人安排分配自己的時間與任務
水電工結束后需要聯系瓦工進場,如果瓦工發現有遺留問題,需要聯系水電工進行溝通
木工需要聯系瓦工確認進展情況,油漆工又需要確認木工狀況...
你會發現他們必須要經常保持聯系,以獲得進展情況,進而安排自己的工作
一個包工隊尚且如此,如果是一個大的裝修公司,怎么辦?
而且裝修而言,階段之間還會有順序,油漆工用不到聯系水電工
但是在系統中,對象豈會僅僅與一個對象聯系?
那豈不是更復雜、亂套?
中介者模式就是為了解決系統內部的調度問題,降低系統內部各模塊之間的耦合度。
裝修公司的項目經理、小組組長、班長,團隊leader等其實這都是中介者模式的體現。
有很多書中以“房屋中介”作為中介者模式的一種場景
個人認為對於某一個房東或者租客而言,“房屋中介”的含義是為你服務的中介人員,此時的含義更接近代理模式
而從廣義上看,有很多租客、買家,也存在很多房東,“房屋中介”將他們聯系在一起,此時的“房租中介”應該是中介公司,這時才更符合中介者模式的含義
中介者模式的重點在於“調度、協調”,含義更接近“指揮中心”,
被指揮的是該系統內部的成員
如果在一個系統中對象之間存在多對多的相互關系
我們可以將對象之間的一些交互行為從各個對象中分離出來,並集中封裝在一個中介者對象中,並由該中介者進行統一協調
如上圖所示,
對象之間多對多的復雜關系就轉化為相對簡單的一對多關系
簡化了對象之間的復雜交互
顯然,中介者模式是
迪米特法則(不要和陌生人說話)的典型。
結構
同事角色Colleague
系統中所有組成部件的抽象角色
具體的同事角色ConcreteColleague
系統的每一個具體的組成部分,就像公司的每個同事
提供自身職責功能的方法接口,供中介者調用
定義中介者到同事對象的接口,也就是提供接口給中介者調用
中介者(項目經理)根據你的技能分配任務,也就是調用你的方法
中介者角色Mediator
定義了同事Colleague對象到中介者的接口,也就是所有同事通信的接口(同事間的通信借助於中介者提供的這個方法)
也就是提供一個方法給同事們調用,用來請求其他同事協助協助,這個方法是中介者提供的
這個方法典型的示例就是事件處理方法
具體的中介者ConcreteMediator
具體的中介者,實現Mediator定義的接口,協調各同事進行協作
所有的成員之間,可以相互協調工作,但是卻又不直接相互管理
這些對象都與項目經理“中介者”進行緊密聯系
由項目經理進行工作協調,每個組成部分就如同我們項目組中的一個成員,也就是同事一樣,這也是上文中Colleague 角色的由來
如何相互協調工作但是卻又不直接相互管理?比如
class A{ void f(){ //do sth B b = new B(); b.g(); }
上面偽代碼中
類A有一個方法f ,做了一些事情之后,創建了一個B的對象b,然后調用b的方法g,做了一些處理
這就是A與B的協作,A也同時具有管理B的職責
如果轉換為下面的形式,就是中介者模式
A和B的協作不在具有對象管理關系,而是項目經理Mediator統一進行管理
class Mediator{ A a = new A(); B b = new B(); void cooperation(){ a.f(); b.g(); } }
代碼示例
使用《設計模式 可復用面向對象軟件的基礎》中的例子為原型
考慮一個圖形用戶界面中對話框的實現。
對話框使用一個窗口來展現一系列的窗口組件,比如按鈕菜單輸入域等
比如下圖,IDEA的字體設置窗口,當進行Font字體設置時
- 預覽區域內的字體將會發生變化
- 右下角的Apply 應用按鈕將成為可點擊狀態
一種可能的解決方法
package mediator.simple; /** * 設置字體類,提供字體設置方法. * 並且創建展示Display對象,調用reDisplay方法重新展示 * 並且創建按鈕Button對象,調用applyButton方法使能應用按鈕 */ public class Font { public void setFont() { System.out.println("設置字體..."); Display display = new Display(); display.reDisplay(); Button button = new Button(); button.applyButton(); } }
package mediator.simple; public class Display { public void reDisplay() { System.out.println("字體重新展示..."); } }
package mediator.simple; public class Button { public void applyButton() { System.out.println("應用按鈕可用..."); } }
package mediator.simple; public class Test { public static void main(String[] args) { Font font = new Font(); font.setFont(); } }
上面的示例很簡單
為了實現“點擊設置字體,選擇字體后預覽框字體的改變以及使能應用按鈕的功能”
也就是
聯動的功能
設置字體后,分別創建展示和按鈕對象,調用對象的方法
很顯然,
字體不僅操心自己的事情,還管理着展示Display和按鈕Button
而且,如果直接點擊取消會發生什么?一切將會還原,又伴隨着一系列的調用
難道仍舊需要:“不僅操心自己的事情,還要負責管理別人么”?
就像沒有項目經理的包工隊一樣了,既操心自己又要管理別人
成了我們上面所說的網狀結構,內部各個同事之間的耦合度極高
重構中介者模式
重構的業務邏輯:
- 通過引入mediator中介者,作為同事之間協作的中間人,提供operation()方法,用於同事間請求協助、事件處理
- 每個同事類都知道這個中介,所以在抽象角色Colleague中設置了Mediator屬性,構造方法注入,並且提供notifyEvent方法,封裝了mediator的operation()方法
- 當具體的同事ConcreteColleague,執行操作后,需要其他同事協作時,直接調用notifyEvent()方法
- 每個具體的同事提供自身的職責接口
簡單地說就是,提供中介者“項目經理”mediator,提供事件處理方法
所有的同事都只知道“項目經理”,如果有事需要其他同事辦,就叫“項目經理”。
Mediator抽象中介者角色定義了operation方法
各個Colleague對象通過此方法進行通信,接受參數類型為Colleague的對象
package mediator; public abstract class Mediator { abstract void operation(Colleague event); }
Colleague抽象同事角色擁有Mediator,通過構造方法注入
提供了notifyEvent方法,調用中介者的operation方法,並且將自身作為參數
package mediator; public abstract class Colleague { private Mediator mediator; Colleague(Mediator mediator) { this.mediator = mediator; } public void notifyEvent() { mediator.operation(this); } }
package mediator; public class Button extends Colleague { Button(Mediator mediator){ super(mediator); } public void applyButton() { System.out.println("應用按鈕可用..."); } }
package mediator; public class Display extends Colleague { Display(Mediator mediator) { super(mediator); } public void reDisplay() { System.out.println("字體重新展示..."); } }
package mediator; public class Font extends Colleague { private String fontName; public String getFontName() { return fontName; } Font(Mediator mediator) { super(mediator); } public void changeFont() { System.out.println("設置字體......"); fontName = "微軟雅黑"; notifyEvent(); } }
ConcreteMediator實現了Mediator定義的接口
並且內部維護三個對象
如果事件類型是Font,那么調用設置字體的事件
package mediator; public class ConcreteMediator extends Mediator { private Button button; private Display display; private Font font; ConcreteMediator() { button = new Button(this); display = new Display(this); font = new Font(this); } @Override void operation(Colleague event) { if (event instanceof Font) { setFontEvent(event); } } private void setFontEvent(Colleague event) { System.out.println(((Font) event).getFontName()); button.applyButton(); display.reDisplay(); } }
測試代碼
package mediator; public class Test { public static void main(String[] args){ Mediator mediator = new ConcreteMediator(); Font font = new Font(mediator); font.changeFont(); } }
上面的示例中,以設置字體為例,當字體變化時,請求“項目經理”安排其他同事協助
“項目經理”operation(Colleague event) 發現是設置字體的事件后,調用對應的事件處理方法,也就是尋找其他同事進行協助
中介者模式將每個場景中對象之間的協作進行封裝
小結
當你需要其他同事協助時,肯定不需要項目經理每次都創建具體的同事對象
上面的示例中,在ConcreteMediator的構造方法中創建的各個具體同事的實例(可以理解為項目經理手下有你和你的一幫同事)
你還
可以提取出來工廠類用於管理同事類和中介者類
比如通過一個工廠對象管理各個同事實例(如果可以還可以將各個同事都設置為單例 )
並且中介者類作為這個工廠對象的內部類
同事對象的傳遞可以達到信息的獲取與回調
通過給Font加了一個fontName屬性,通過打印信息可以看得出來
通過將當時發生事件的對象傳遞了過來,可以獲得事件的更多信息,可以根據信息進一步的進行操作
比如,此處的設置了字體,設置了什么字體?這一進一步的信息就通過參數攜帶進來
而且,這種方式還可以通知到Font本身
也就是可以在setFontEvent(Colleague event) 事件處理中,調用Font的方法進行結果返回,有回調的韻味
良好的擴展性
如果需要增加一個新的事件處理過程,比如點擊取消按鈕,還原字體設置,還原預覽按鈕
只需要在Button新增加一個職責(方法),然后在ConcreteMediator中新增加一種類型的事件處理程序即可
上面的示例中僅僅定義了幾個簡單的方法,實踐中每個具體同事角色自然不會僅僅只有幾個方法
不管有多少方法,都可以通過中介者將他們的協作邏輯進行封裝
通過具體的中介者ConcreteMediator的處理方法進行安排
中介者模式可以很好地應用於事件通知的處理
中介者模式時序圖
當某一個同事對象產生事件后,如果需要其他同事進行協助,他將調用中介者的方法
中介者接到消息后,調用其他同事的方法(安排其他同時干活),然后最終還可以將消息進行返回
與門面模式對比
門面模式和中介者模式都是通過中間類降低系統的耦合度
但是門面模式是為了子系統提供一個簡單一致的接口,客戶端透過門面類向子系統發送消息,可以認為消息的發送是單方向的
客戶端--->門面--->子系統方法,子系統提供功能給客戶端
門面是在外部客戶端和內部子系統之間
中介者模式中,則是主要復雜系統內部多個對象之間的相互協作
中介者是各個內部對象同事之間
擴展
任何模式都不是一成不變的,模式結構圖只是一種相對比較合適准確的方案
但是涉及到具體的業務中,一切都是可變的
中介者模式也有多種靈活性
如果只有一個中介者對象,顯然抽象Mediator角色是可以隱藏的
那么ConcreteMediator就兼顧了這個角色,提供通信接口給同事角色
中介者模式的本質含義是負責協調系統內部的多個交互的對象,將同事進行解耦
也就是說
實現共同的接口並不是必須的
而實際上,一個系統中協作的多個對象,很可能是不同的類型,如果去掉了抽象角色Colleague
那么就可以將任意的有關聯的對象組織在一起,通過中介者協同工作
而且,也並不意味着一定要中介者持有同事角色,如果合適,直接創建中介者也並非不可以
雖然上面提到你可以使用另外的工廠管理,那也只是一種常用用法而已。
只需要記住中介者的本質在於“
行為與協作的分離,中介者封裝了對象間的協作”
總結
中介者模式將對象的行為和協作進行分離,使對象更加專注於對象的行為,也就是自身的功能
將協作分離出來封裝到中介者內部
迪米特法則,不要和陌生人講話,只與你的朋友通信,
中介者模式是迪米特法則的典型應用
通過引入中介者對象,在同事之間的請求調用中,增加了“項目經理”,如果有事搞不定,需要協助,那么就找他
每個人只和項目經理對話,也就是僅僅和項目經理這個朋友聊天,其他的同事都不理了  ̄□ ̄||
中介者將網狀結構,轉換為了星型結構
將多對多的關系,轉換為了一對多的關系
所以中介者模式將系統中對象對其他對象的引用數目降到最低,簡化了對象之間的交互。
中介者模式將各個同事對象之間進行解耦,增加新的中介者或者同事都比較方便,符合開閉原則
MVC模式,也是一種典型的中介者模式,控制器負責視圖層和模型層的調度處理,是一個明顯的中介者。
中介者模式將同事進行解耦,但是各個Colleague類必須同中介者進行交互
更准確的說,解耦后的關系植入到了中介者自身,如果原來的同事對象間的相互調用非常復雜
那么,這個中介者也很可能非常的復雜難以維護
換言之,
同事間的復雜性與耦合性轉移到了中介者內部
中介者內部對於各個同事之間的協調代碼不太可能復用,所以
具體同事類的復用性也是以中介者的不可復用為代價的
如果系統中的各個對象之間存在復雜的引用關系,希望能夠通過中間類將他們進行解耦
或者系統中對象間的引用混亂,交互太多,導致難以進行類的復用
你就可以考慮中介者模式
但是並不意味着任何涉及到多個類交互的地方都用中介者模式,如果原本並不復雜,使用中介者將會增加復雜度
基本前提就是緊密耦合,比如出現了網狀結構