用一箇中介對象(中介者)來封裝一系列的對象交互,中介者使各對象不須要顯式地相互引用,從而使其耦合鬆散
並且能夠獨立地改變它們之間的交互。
中介者模式又稱爲調停者模式。
面向對象的程序設計中,咱們一般將功能進行分解,按照職責以類爲維度進行劃分,也就是使用時功能最終將分佈在多個對象中
而且咱們會盡量的保持對象功能的單一(單一職責原則)
相對於對象的單一職責來講,任何的系統或者模塊的功能卻並不會單一,每每都是有多個對象交互協做來實現全部的功能
對象之間不可避免的須要創建鏈接
換句話說
系統(或者某模塊)必然是複雜的(沒有什麼系統能夠幾個對象就輕鬆搞定,那樣或許也不能稱之爲系統了吧)
功能必然會分佈在多個對象中
多個對象協做必然須要聯繫,這必然致使耦合的產生
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(www.dasheng178.com/ ) {
System.out.println("應用按鈕可用...");
}
}
複製代碼
複製代碼
package mediator;
public class Display extends Colleague {
Display(Mediator www.michenggw.com/ mediator) {
super(mediator);
}
public void reDisplay(www.mcyllpt.com/ ) {
System.out.println("字體從新展現...");
}
}
複製代碼
複製代碼
package mediator;
public class Font extends Colleague {
private String fontName;
public String getFontName(www.gcyl152.com) {
return fontName;
}
Font(Mediator mediator) {
super(mediator);
}
public void changeFont() {
System.out.yongshiyule178.com/ 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 行爲型 設計模式(二十一)設計模式