中介者模式是一種常見的解耦思維的模式,本文對中介者模式進行了簡單介紹,給出了中介者模式的意圖,結構,以及意圖發展演變,給出了中介者模式的Java代碼示例,中介者模式又稱為調停者模式,是迪米特法則的詮釋。 ...
中介者模式(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()方法
- 每個具體的同事提供自身的職責介面
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) 發現是設置字體的事件後,調用對應的事件處理方法,也就是尋找其他同事進行協助 中介者模式將每個場景中對象之間的協作進行封裝