中介者模式 調停者 Mediator 行為型 設計模式(二十一)


 
中介者模式(Mediator)
 
調度、調停
 
image_5c171b2a_4f0c

意圖

用一個中介對象(中介者)來封裝一系列的對象交互,中介者使各對象不需要顯式地相互引用,從而使其耦合松散
而且可以獨立地改變它們之間的交互。
中介者模式又稱為調停者模式。
 
面向對象的程序設計中,我們通常將功能進行分解,按照職責以類為維度進行划分,也就是使用時功能最終將分布在多個對象中
並且我們會盡可能的保持對象功能的單一(單一職責原則)
相對於對象的單一職責來說,任何的系統或者模塊的功能卻並不會單一,往往都是有多個對象交互協作來實現所有的功能
對象之間不可避免的需要建立連接
換句話說
系統(或者某模塊)必然是復雜的(沒有什么系統可以幾個對象就輕松搞定,那樣或許也不能稱之為系統了吧)
功能必然會分布在多個對象中
多個對象協作必然需要聯系 必然導致耦合的產生
image_5c171b2a_724a
如上圖所示,雖然系統對外呈現是一個統一的整體,但是,內部各個模塊之間很可能是緊密的耦合 
各個 模塊相互聯系,可能互相持有引用,會出現網狀結構,完全不符合迪米特法則
如果 對系統進行改動,將會變得困難

我們以裝修為例
一般裝修公司都會給每一個項目配備一個項目經理(這個項目也就是你家這個單子了,項目經理就是包工頭)
裝修的一般階段分為:前期設計→拆改→水電→瓦工→木工→油漆→安裝→保潔→軟裝
 
項目經理手上經常同時有幾個工地在同步進行,只要錯的開就好了
因為每個階段都是有先后順序的,你不可能先木工,然后再去拆改;
因為每個階段也都需要一定時間,也意味着這一撥人不可能同時在你家工作
 
開工后項目經理會進行工作安排
水電工結束了A之后,項目經理會安排他到B,然后安排瓦工到A,然后........
所有的順序都是由項目經理負責調度,水電工可以完全不認識瓦工,他們也完全不需要進行聯系
有事兒找項目經理
 
如果沒有項目經理,會是什么場景?
那就是人人都是項目經理,人人都需要管自己,還需要管別人
也就是每個人安排分配自己的時間與任務
水電工結束后需要聯系瓦工進場,如果瓦工發現有遺留問題,需要聯系水電工進行溝通
木工需要聯系瓦工確認進展情況,油漆工又需要確認木工狀況...
你會發現他們必須要經常保持聯系,以獲得進展情況,進而安排自己的工作
 
一個包工隊尚且如此,如果是一個大的裝修公司,怎么辦?
而且裝修而言,階段之間還會有順序,油漆工用不到聯系水電工
但是在系統中,對象豈會僅僅與一個對象聯系?
那豈不是更復雜、亂套?

 
中介者模式就是為了解決系統內部的調度問題,降低系統內部各模塊之間的耦合度。
裝修公司的項目經理、小組組長、班長,團隊leader等其實這都是中介者模式的體現。
 
有很多書中以“房屋中介”作為中介者模式的一種場景
個人認為對於某一個房東或者租客而言,“房屋中介”的含義是為你服務的中介人員,此時的含義更接近代理模式
而從廣義上看,有很多租客、買家,也存在很多房東,“房屋中介”將他們聯系在一起,此時的“房租中介”應該是中介公司,這時才更符合中介者模式的含義
中介者模式的重點在於“調度、協調”,含義更接近“指揮中心”被指揮的是該系統內部的成員
 
如果在一個系統中對象之間存在多對多的相互關系
我們可以將對象之間的一些交互行為從各個對象中分離出來,並集中封裝在一個中介者對象中,並由該中介者進行統一協調
image_5c171b2a_66af
如上圖所示, 對象之間多對多的復雜關系就轉化為相對簡單的一對多關系
簡化了對象之間的復雜交互
顯然,中介者模式是 迪米特法則(不要和陌生人說話)的典型。  

結構  

image_5c171b2a_260e
同事角色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 應用按鈕將成為可點擊狀態
image_5c171b2b_350a
 
一種可能的解決方法
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();
    }
} 
image_5c171b2b_398c
上面的示例很簡單
為了實現“點擊設置字體,選擇字體后預覽框字體的改變以及使能應用按鈕的功能”
也就是 聯動的功能
設置字體后,分別創建展示和按鈕對象,調用對象的方法
很顯然, 字體不僅操心自己的事情,還管理着展示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();
    }
}

 

image_5c171b2b_3c66
上面的示例中,以設置字體為例,當字體變化時,請求“項目經理”安排其他同事協助
“項目經理”operation(Colleague event)  發現是設置字體的事件后,調用對應的事件處理方法,也就是尋找其他同事進行協助
 
中介者模式將每個場景中對象之間的協作進行封裝  

小結

當你需要其他同事協助時,肯定不需要項目經理每次都創建具體的同事對象
上面的示例中,在ConcreteMediator的構造方法中創建的各個具體同事的實例(可以理解為項目經理手下有你和你的一幫同事)
你還 可以提取出來工廠類用於管理同事類和中介者類
比如通過一個工廠對象管理各個同事實例(如果可以還可以將各個同事都設置為單例 )
並且中介者類作為這個工廠對象的內部類
 
同事對象的傳遞可以達到信息的獲取與回調
通過給Font加了一個fontName屬性,通過打印信息可以看得出來
通過將當時發生事件的對象傳遞了過來,可以獲得事件的更多信息,可以根據信息進一步的進行操作
比如,此處的設置了字體,設置了什么字體?這一進一步的信息就通過參數攜帶進來
而且,這種方式還可以通知到Font本身
也就是可以在setFontEvent(Colleague event) 事件處理中,調用Font的方法進行結果返回,有回調的韻味
 
良好的擴展性
如果需要增加一個新的事件處理過程,比如點擊取消按鈕,還原字體設置,還原預覽按鈕
只需要在Button新增加一個職責(方法),然后在ConcreteMediator中新增加一種類型的事件處理程序即可
 
上面的示例中僅僅定義了幾個簡單的方法,實踐中每個具體同事角色自然不會僅僅只有幾個方法
不管有多少方法,都可以通過中介者將他們的協作邏輯進行封裝
通過具體的中介者ConcreteMediator的處理方法進行安排
中介者模式可以很好地應用於事件通知的處理

中介者模式時序圖

image_5c171b2b_1ce5
當某一個同事對象產生事件后,如果需要其他同事進行協助,他將調用中介者的方法
中介者接到消息后,調用其他同事的方法(安排其他同時干活),然后最終還可以將消息進行返回

與門面模式對比

image_5c171b2b_31b0
門面模式和中介者模式都是通過中間類降低系統的耦合度
但是門面模式是為了子系統提供一個簡單一致的接口,客戶端透過門面類向子系統發送消息,可以認為消息的發送是單方向的
客戶端--->門面--->子系統方法,子系統提供功能給客戶端
門面是在外部客戶端和內部子系統之間
中介者模式中,則是主要復雜系統內部多個對象之間的相互協作
中介者是各個內部對象同事之間  

擴展

任何模式都不是一成不變的,模式結構圖只是一種相對比較合適准確的方案
但是涉及到具體的業務中,一切都是可變的
中介者模式也有多種靈活性
 
如果只有一個中介者對象,顯然抽象Mediator角色是可以隱藏的
那么ConcreteMediator就兼顧了這個角色,提供通信接口給同事角色
 
中介者模式的本質含義是負責協調系統內部的多個交互的對象,將同事進行解耦
也就是說 實現共同的接口並不是必須的
而實際上,一個系統中協作的多個對象,很可能是不同的類型,如果去掉了抽象角色Colleague
那么就可以將任意的有關聯的對象組織在一起,通過中介者協同工作
 
而且,也並不意味着一定要中介者持有同事角色,如果合適,直接創建中介者也並非不可以
雖然上面提到你可以使用另外的工廠管理,那也只是一種常用用法而已。
 
只需要記住中介者的本質在於“ 行為與協作的分離,中介者封裝了對象間的協作

總結

中介者模式將對象的行為和協作進行分離,使對象更加專注於對象的行為,也就是自身的功能
將協作分離出來封裝到中介者內部
 
迪米特法則,不要和陌生人講話,只與你的朋友通信, 中介者模式是迪米特法則的典型應用
通過引入中介者對象,在同事之間的請求調用中,增加了“項目經理”,如果有事搞不定,需要協助,那么就找他 
每個人只和項目經理對話,也就是僅僅和項目經理這個朋友聊天,其他的同事都不理了    ̄□ ̄||
 
中介者將網狀結構,轉換為了星型結構
將多對多的關系,轉換為了一對多的關系
所以中介者模式將系統中對象對其他對象的引用數目降到最低,簡化了對象之間的交互
image_5c171b2b_3f1e
中介者模式將各個同事對象之間進行解耦,增加新的中介者或者同事都比較方便,符合開閉原則
MVC模式,也是一種典型的中介者模式,控制器負責視圖層和模型層的調度處理,是一個明顯的中介者。
 
中介者模式將同事進行解耦,但是各個Colleague類必須同中介者進行交互
更准確的說,解耦后的關系植入到了中介者自身,如果原來的同事對象間的相互調用非常復雜
那么,這個中介者也很可能非常的復雜難以維護
 
換言之, 同事間的復雜性與耦合性轉移到了中介者內部
中介者內部對於各個同事之間的協調代碼不太可能復用,所以 具體同事類的復用性也是以中介者的不可復用為代價的
 
如果系統中的各個對象之間存在復雜的引用關系,希望能夠通過中間類將他們進行解耦
或者系統中對象間的引用混亂,交互太多,導致難以進行類的復用
你就可以考慮中介者模式
但是並不意味着任何涉及到多個類交互的地方都用中介者模式,如果原本並不復雜,使用中介者將會增加復雜度
基本前提就是緊密耦合,比如出現了網狀結構


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM