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

来源:https://www.cnblogs.com/noteless/archive/2018/12/17/10130616.html
-Advertisement-
Play Games

中介者模式是一種常見的解耦思維的模式,本文對中介者模式進行了簡單介紹,給出了中介者模式的意圖,結構,以及意圖發展演變,給出了中介者模式的Java代碼示例,中介者模式又稱為調停者模式,是迪米特法則的詮釋。 ...


  中介者模式(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類必須同中介者進行交互 更準確的說,解耦後的關係植入到了中介者自身,如果原來的同事對象間的相互調用非常複雜 那麼,這個中介者也很可能非常的複雜難以維護   換言之,同事間的複雜性與耦合性轉移到了中介者內部 中介者內部對於各個同事之間的協調代碼不太可能復用,所以具體同事類的復用性也是以中介者的不可復用為代價的   如果系統中的各個對象之間存在複雜的引用關係,希望能夠通過中間類將他們進行解耦 或者系統中對象間的引用混亂,交互太多,導致難以進行類的復用 你就可以考慮中介者模式 但是並不意味著任何涉及到多個類交互的地方都用中介者模式,如果原本並不複雜,使用中介者將會增加複雜度 基本前提就是緊密耦合,比如出現了網狀結構 原文地址:中介者模式 調停者 Mediator 行為型 設計模式(二十一)
您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 首先給一個神奇的圖: 我的反應,精分吧!一會兒true一會兒false的。。。 後來發現,把g去掉後就正常了,那這是為什麼呢??lastIndex惹得鬼! 正文: lastIndex 全局正則表達是,有一個屬性:lastIndex,這個屬性是用來存放上一次匹配文本之後的第一個字元的位置。 exec( ...
  • 通過 jQuery,可以很容易地添加新元素/內容。 添加新的 HTML 內容 一共有四種方法: append() - 在被選元素的結尾插入內容 prepend() - 在被選元素的開頭插入內容 after() - 在被選元素之後插入內容 before() - 在被選元素之前插入內容 以下為jQuer ...
  • 閉包在紅寶書中的解釋就是:有權訪問另一個函數作用域中的變數的函數。 1.變數作用域 全局變數:所有的函數外部定義的變數,它的作用域是整個script。 局部變數:定義在函數體內部的變數,作用域僅限於函數體內部。離開函數體就會無效。再調用就是出錯。 舉例如下-局部變數: a變數定義在fun函數內,是局 ...
  • 什麼是瀏覽器內核 瀏覽器內核的分類 瀏覽器內核的作用 W3C 帶著問題去瞭解 1、什麼是瀏覽器內核? 瀏覽器端內核就是開發商按照約定好的規則開發出的能夠將正確開發者代碼渲染成頁面的東西 2、瀏覽器內核的分類: webkit內核:chrome瀏覽器及大部分安卓手機、safari Gecko內核:FF ...
  • java深克隆和淺克隆 基本概念 1. 被覆制對象的所有變數都含有與原來的對象相同的值,而所有的對其他對象的引用仍然指向原來的對象。換言之,淺複製僅僅複製所拷貝的對象,而不複製它所引用的對象。 1. 被覆制對象的所有變數都含有與原來的對象相同的值,除去那些引用其他對象的變數。那些引用其他對象的變數將 ...
  • 轉載請標明出處: https://www.fangzhipeng.com 本文出自 "方誌朋的博客" 在上一篇文章詳細的介紹了Gateway的Predict,Predict決定了請求由哪一個路由處理,在路由處理之前,需要經過“pre”類型的過濾器處理,處理返迴響應之後,可以由“post”類型的過濾器 ...
  • 一、適配器模式 1、概念 定義:將一個類的介面,轉換成客戶期望的另一個類的介面,適配器讓原本介面不相容的類可以合作無間。 安卓轉Type C頭,就是一個典型的適配器模式。在安卓頭和 Type C 之間引入適配器,安卓頭是被適配者。 解析:   1、 客戶(Client)通過目標接 ...
  • Zookeeper 的核心原理 Zookeeper 的由來 各個節點的數據一致性 怎麼保證任務只在一個節點執行 如果orderserver1掛了,其他節點如何發現並接替 存在共用資源,互斥性、安全性 Apache 的Zookeeper Google 的Chubby 是一個分散式鎖服務,通過Googl ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...