設計模式-中介者模式(Mediator)-Java

設計模式-中介者模式(Mediator)-Java


目錄




內容

1、前言

  騰訊公司推出的QQ作爲一款免費的即時聊天軟件深受廣大用戶的喜愛,它已經成爲很多人學習、工作和生活的一部分(不要告訴我你沒有QQ哦)。在QQ聊天中,一般有兩種聊天方式:第一種是用戶與用戶直接聊天,第二種是通過QQ羣聊天,如圖20-1所示。如果我們使用圖1-1(A)所示方式,一個用戶如果要與別的用戶聊天或發送文件,通常需要加其他用戶爲好友,用戶與用戶之間存在多對多的聯繫,這將導致系統中用戶之間的關係非常複雜,一個用戶如果要將相同的信息或文件發送給其他所有用戶,必須一個一個的發送,於是QQ羣產生了,如圖1-1(B)所示,如果使用QQ羣,一個用戶就可以向多個用戶發送相同的信息和文件而無須一一進行發送,只需要將信息或文件發送到羣中或作爲羣共享即可,羣的作用就是將發送者所發送的信息和文件轉發給每一個接收者用戶。通過引入羣的機制,將極大減少系統中用戶之間的兩兩通信,用戶與用戶之間的聯繫可以通過羣來實現。
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-sKflWqBw-1592104315777)(./images/qq聊天.png)]圖1-1 QQ聊天示意圖

  在有些軟件中,某些類/對象之間的相互調用關係錯綜複雜,類似QQ用戶之間的關係,此時,我們特別需要一個類似“QQ羣”一樣的中間類來協調這些類/對象之間的複雜關係,以降低系統的耦合度。有一個設計模式正爲此而誕生,它就是本章將要介紹的中介者模式。

2、示例案例-客戶信息管理窗口

  Sunny軟件公司欲開發一套CRM系統,其中包含一個客戶信息管理模塊,所設計的“客戶信息管理窗口”界面效果圖如圖2-1所示:
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-YZYLBQP1-1592104315782)(./images/客戶信息管理.png)]
圖2-1 客戶信息管理

  Sunny公司開發人員通過分析發現,在圖20-2中,界面組件之間存在較爲複雜的交互關係:如果刪除一個客戶,要在客戶列表(List)中刪掉對應的項,客戶選擇組合框(ComboBox)中客戶名稱也將減少一個;如果增加一個客戶信息,客戶列表中需增加一個客戶,且組合框中也將增加一項。如果實現界面組件之間的交互是Sunny公司開發人員必須面對的一個問題?

Sunny公司開發人員對組件之間的交互關係進行了分析,結果如下:

  • (1) 當用戶單擊“增加”按鈕、“刪除”按鈕、“修改”按鈕或“查詢”按鈕時,界面左側的“客戶選擇組合框”、“客戶列表”以及界面中的文本框將產生響應。
  • (2) 當用戶通過“客戶選擇組合框”選中某個客戶姓名時,“客戶列表”和文本框將產生響應。+ + (3) 當用戶通過“客戶列表”選中某個客戶姓名時,“客戶選擇組合框”和文本框將產生響應。於是,Sunny公司開發人員根據組件之間的交互關係繪製瞭如圖2-2所示初始類圖:
    [外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-LXz1xWax-1592104315783)(./images/客戶信息管理類.png)]
    圖2-2 客戶信息管理類圖

與類圖20-3所對應的框架代碼片段如下:

//按鈕類
class Button {
private List list;
private ComboBox cb;
private TextBox tb;
......
//界面組件的交互
public void change() {
list.update();
cb.update();
tb.update();
}
public void update() {
......
}
......
}
//列表框類
class List {
private ComboBox cb;
private TextBox tb;
......
//界面組件的交互
public void change() {
cb.update();
tb.update();
}
public void update() {
......
}
......
}
//組合框類
class ComboBox {
private List list;
private TextBox tb;
......
//界面組件的交互
public void change() {
list.update();
tb.update();
}
public void update() {
......
}
......
}
//文本框類
class TextBox {
public void update() {
......
}
......
}

  分析圖2-2所示初始結構圖和上述代碼,我們不難發現該設計方案存在如下問題:

  • (1) 系統結構複雜且耦合度高:每一個界面組件都與多個其他組件之間產生相互關聯和調用,若一個界面組件對象發生變化,需要跟蹤與之有關聯的其他所有組件並進行處理,系統組件之間呈現一種較爲複雜的網狀結構,組件之間的耦合度高。
  • (2) 組件的可重用性差:由於每一個組件和其他組件之間都具有很強的關聯,若沒有其他組件的支持,一個組件很難被另一個系統或模塊重用,這些組件表現出來更像一個不可分割的整體,而在實際使用時,我們往往需要每一個組件都能夠單獨重用,而不是重用一個由多個組件組成的複雜結構。
  • (3) 系統的可擴展性差:如果在上述系統中增加一個新的組件類,則必須修改與之交互的其他組件類的源代碼,將導致多個類的源代碼需要修改,同樣,如果要刪除一個組件也存在類似的問題,這違反了“開閉原則”,可擴展性和靈活性欠佳。

  由於存在上述問題,Sunny公司開發人員不得不對原有系統進行重構,那如何重構呢?大家想到了“迪米特法則”,引入一個“第三者”來降低現有系統中類之間的耦合度。由這個“第三者”來封裝並協調原有組件兩兩之間複雜的引用關係,使之成爲一個松耦合的系統,這個“第三者”又稱爲“中介者”,中介者模式因此而得名。下面讓我們正式進入中介者模式的學習,學會如何使用中介者類來協調多個類/對象之間的交互,以達到降低系統耦合度的目的。

3、中介者模式概述

  如果在一個系統中對象之間的聯繫呈現爲網狀結構,如圖3-1所示。對象之間存在大量的多對多聯繫,將導致系統非常複雜,這些對象既會影響別的對象,也會被別的對象所影響,這些對象稱爲同事對象,它們之間通過彼此的相互作用實現系統的行爲。在網狀結構中,幾乎每個對象都需要與其他對象發生相互作用,而這種相互作用表現爲一個對象與另外一個對象的直接耦合,這將導致一個過度耦合的系統。
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-tC8o1b1N-1592104315784)(./images/對象存在複雜關係的網狀結構.png)]
圖3-1 對象之間存在複雜關係的網狀結構

  中介者模式可以使對象之間的關係數量急劇減少,通過引入中介者對象,可以將系統的網狀結構變成以中介者爲中心的星形結構,如圖20-5所示。在這個星形結構中,同事對象不再直接與另一個對象聯繫,它通過中介者對象與另一個對象發生相互作用。中介者對象的存在保證了對象結構上的穩定,也就是說,系統的結構不會因爲新對象的引入帶來大量的修改工作。
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-7klp358i-1592104315785)(./images/引入中介者對象的星型結構.png)]
圖3-2 引入中介者對象的星型結構

  如果在一個系統中對象之間存在多對多的相互關係,我們可以將對象之間的一些交互行爲從各個對象中分離出來,並集中封裝在一箇中介者對象中,並由該中介者進行統一協調,這樣對象之間多對多的複雜關係就轉化爲相對簡單的一對多關係。通過引入中介者來簡化對象之間的複雜交互,中介者模式是“迪米特法則”的一個典型應用。

3.1、中介者模式定義

  • 中介者模式(Mediator Pattern):用一箇中介對象(中介者)來封裝一系列的對象交互,中介者使各對象不需要顯示地相互引用,從而使其耦合鬆散,而且可以獨立地改變它們之間的交互。終結者模式又稱爲調停者模式,塔是一種對象行爲型模式。

在中介者模式中,我們引入了用於協調其他對象/類之間相互調用的中介者類,爲了讓系統具有更好的靈活性和可擴展性,通常還提供了抽象中介者,其結構圖如圖3.2-1所示:

3.2、中介者模式結構

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-21OLLlZm-1592104315787)(./images/model_mediator.png)]
圖3.2-1中介者模式結構圖

3.3、中介者模式結構圖中角色

  在中介者模式結構圖中包含如下幾個角色:

  • Mediator(抽象中介者):它定義一個接口,該接口用於與各同事對象之間進行通信。
  • ConcreteMediator(具體中介者):它是抽象中介者的子類,通過協調各個同事對象來實現協作行爲,它維持了對各個同事對象的引用。
  • Colleague(抽象同事類):它定義各個同事類公有的方法,並聲明瞭一些抽象方法來供子類實現,同時它維持了一個對抽象中介者類的引用,其子類可以通過該引用來與中介者通信。
  • ConcreteColleague(具體同事類):它是抽象同事類的子類;每一個同事對象在需要和其他同事對象通信時,先與中介者通信,通過中介者來間接完成與其他同事類的通信;在具體同事類中實現了在抽象同事類中聲明的抽象方法

  中介者模式的核心在於中介者類的引入,在中介者模式中,中介者類承擔了兩方面的職責:

  • (1) 中轉作用(結構性):通過中介者提供的中轉作用,各個同事對象就不再需要顯式引用其他同事,當需要和其他同事進行通信時,可通過中介者來實現間接調用。該中轉作用屬於中介者在結構上的支持。
  • (2) 協調作用(行爲性):中介者可以更進一步的對同事之間的關係進行封裝,同事可以一致的和中介者進行交互,而不需要指明中介者需要具體怎麼做,中介者根據封裝在自身內部的協調邏輯,對同事的請求進行進一步處理,將同事成員之間的關係行爲進行分離和封裝。該協調作用屬於中介者在行爲上的支持

3.4、中介者模式典型實現

  在中介者模式中,典型的抽象中介者類代碼如下所示:

abstract class Colleague {
protected Mediator mediator; //維持一個抽象中介者的引用
public Colleague(Mediator mediator) {
this.mediator=mediator;
}
public abstract void method1(); //聲明自身方法,處理自己的行爲
//定義依賴方法,與中介者進行通信
public void method2() {
mediator.operation();
}
}

在抽象同事類中聲明瞭同事類的抽象方法,而在具體同事類中將實現這些方法,典型的具體
同事類代碼如下所示:

class ConcreteColleague extends Colleague {
public ConcreteColleague(Mediator mediator) {
super(mediator);
}
//實現自身方法
public void method1() {
......
}
}

  在具體同事類ConcreteColleague中實現了在抽象同事類中聲明的方法,其中方法method1()是同事類的自身方法(Self-Method),用於處理自己的行爲,而方法method2()是依賴方法(DependMethod),用於調用在中介者中定義的方法,依賴中介者來完成相應的行爲,例如調用另一個同事類的相關方法。

4、中介者模式完整解決方案-客戶信息管理窗口

  爲了協調界面組件對象之間的複雜交互關係,Sunny公司開發人員使用中介者模式來設計客戶信息管理窗口,其結構示意圖如圖4-1所示:
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-rro6Juw7-1592104315788)(./images/客戶信息管理_midiator.png)]
圖4-1 引入中介者類的客戶信息管理窗口結構示意圖
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-UxoGtD86-1592104315790)(./images/客戶信息管理_restructure.png)]
圖4-2 重構後的客戶信息管理窗口結構圖

  在圖4-2中,Component充當抽象同事類,Button、List、ComboBox和TextBox充當具體同事類,Mediator充當抽象中介者類,ConcreteMediator充當具體中介者類,ConcreteMediator維持了對具體同事類的引用,爲了簡化ConcreteMediator類的代碼,我們在其中只定義了一個Button對象和一個TextBox對象。完整代碼如下所示:

//抽象中介者
abstract class Mediator {
public abstract void componentChanged(Component c);
}
//具體中介者
class ConcreteMediator extends Mediator {
//維持對各個同事對象的引用
public Button addButton;
public List list;
public TextBox userNameTextBox;
public ComboBox cb;
//封裝同事對象之間的交互
public void componentChanged(Component c) {
//單擊按鈕
if(c == addButton) {
System.out.println("--單擊增加按鈕--");
list.update();
cb.update();
userNameTextBox.update();
}
//從列表框選擇客戶
else if(c == list) {
System.out.println("--從列表框選擇客戶--");
cb.select();
協調多個對象之間的交互——中介者模式(三)
336
userNameTextBox.setText();
}
//從組合框選擇客戶
else if(c == cb) {
System.out.println("--從組合框選擇客戶--");
cb.select();
userNameTextBox.setText();
}
}
}
//抽象組件類:抽象同事類
abstract class Component {
protected Mediator mediator;
public void setMediator(Mediator mediator) {
this.mediator = mediator;
}
//轉發調用
public void changed() {
mediator.componentChanged(this);
}
public abstract void update();
}
//按鈕類:具體同事類
class Button extends Component {
public void update() {
//按鈕不產生交互
}
}
//列表框類:具體同事類
class List extends Component {
public void update() {
System.out.println("列表框增加一項:張無忌。");
}
public void select() {
System.out.println("列表框選中項:小龍女。");
}
}
//組合框類:具體同事類
class ComboBox extends Component {
public void update() {
System.out.println("組合框增加一項:張無忌。");
}
public void select() {
System.out.println("組合框選中項:小龍女。");
}
}
//文本框類:具體同事類
class TextBox extends Component {
public void update() {
System.out.println("客戶信息增加成功後文本框清空。");
}
public void setText() {
System.out.println("文本框顯示:小龍女。");
}
}

編寫如下客戶端測試代碼:

class Client {
public static void main(String args[]) {
//定義中介者對象
ConcreteMediator mediator;
mediator = new ConcreteMediator();
//定義同事對象
Button addBT = new Button();
List list = new List();
ComboBox cb = new ComboBox();
TextBox userNameTB = new TextBox();
addBT.setMediator(mediator);
list.setMediator(mediator);
cb.setMediator(mediator);
userNameTB.setMediator(mediator);
mediator.addButton = addBT;
mediator.list = list;
mediator.cb = cb;
mediator.userNameTextBox = userNameTB;
addBT.changed();
System.out.println("-----------------------------");
list.changed();
}
}

編譯並運行程序,輸出結果如下:

--單擊增加按鈕--
列表框增加一項:張無忌。
組合框增加一項:張無忌。
客戶信息增加成功後文本框清空。

-----------------------------
--從列表框選擇客戶--
組合框選中項:小龍女。
文本框顯示:小龍女。

5、中介者與同事類的擴展

  Sunny軟件公司CRM系統的客戶對“客戶信息管理窗口”提出了一個修改意見:要求在窗口的下端能夠及時顯示當前系統中客戶信息的總數。修改之後的界面如圖5-1所示:
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-Wm8rUJbW-1592104315790)(./images/客戶信息管理窗口_changed.png)]
圖5-1 修改之後的客戶信息管理窗口截面圖

  從圖5-1中我們不難發現,可以通過增加一個文本標籤(Label)來顯示客戶信息總數,而且當用戶點擊“增加”按鈕或者“刪除”按鈕時,將改變文本標籤的內容。

  由於使用了中介者模式,在原有系統中增加新的組件(即新的同事類)將變得很容易,我們至少有如下兩種解決方案:

  • 【解決方案一】增加一個界面組件類Label,修改原有的具體中介者類ConcreteMediator,增加一個對Label對象的引用,然後修改componentChanged()方法中其他相關組件對象的業務處理代碼,原有組件類無須任何修改,客戶端代碼也需針對新增組件Label進行適當修改。

+【解決方案二】與方案一相同,首先增加一個Label類,但不修改原有具體中介者類ConcreteMediator的代碼,而是增加一個ConcreteMediator的子類SubConcreteMediator來實現對Label對象的引用,然後在新增的中介者類SubConcreteMediator中通過覆蓋componentChanged()方法來實現所有組件(包括新增Label組件)之間的交互,同樣,原有組件類無須做任何修改,客戶端代碼需少許修改。

  引入Label之後“客戶信息管理窗口”類結構示意圖如圖5-2所示:
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-R7jVDS9I-1592104315791)(./images/客戶信息管理窗口_add_label.png)]
圖5-2 增加Label組件類後的客戶信息管理窗口結構示意圖

  由於【解決方案二】無須修改ConcreteMediator類,更符合“開閉原則”,因此我們選擇該解決方案來對新增Label類進行處理,對應的完整類圖如圖5-3所示:
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-TKAX5oYi-1592104315792)(./images/客戶信息管理窗口_label_complete.png)]
圖5-3 修改之後的客戶信息管理窗口結構圖

  在圖5-3中,新增了具體同事類Label和具體中介者類SubConcreteMediator,代碼如下所示:

//文本標籤類:具體同事類
class Label extends Component {
public void update() {
System.out.println("文本標籤內容改變,客戶信息總數加1。");
}
}
//新增具體中介者類
class SubConcreteMediator extends ConcreteMediator {
//增加對Label對象的引用
public Label label;
public void componentChanged(Component c) {
//單擊按鈕
if(c == addButton) {
System.out.println("--單擊增加按鈕--");
list.update();
cb.update();
userNameTextBox.update();
label.update(); //文本標籤更新
}
//從列表框選擇客戶
else if(c == list) {
System.out.println("--從列表框選擇客戶--");
cb.select();
userNameTextBox.setText();
}
//從組合框選擇客戶
else if(c == cb) {
System.out.println("--從組合框選擇客戶--");
cb.select();
userNameTextBox.setText();
}
}
}

修改客戶端測試代碼:

class Client {
public static void main(String args[]) {
//用新增具體中介者定義中介者對象
SubConcreteMediator mediator;
mediator = new SubConcreteMediator();
Button addBT = new Button();
List list = new List();
ComboBox cb = new ComboBox();
TextBox userNameTB = new TextBox();
Label label = new Label();
addBT.setMediator(mediator);
list.setMediator(mediator);
cb.setMediator(mediator);
userNameTB.setMediator(mediator);
label.setMediator(mediator);
mediator.addButton = addBT;
mediator.list = list;
mediator.cb = cb;
mediator.userNameTextBox = userNameTB;
mediator.label = label;
addBT.changed();
System.out.println("-----------------------------");
list.changed();
}
}

編譯並運行程序,輸出結果如下:

--單擊增加按鈕--
列表框增加一項:張無忌。
組合框增加一項:張無忌。
客戶信息增加成功後文本框清空。
文本標籤內容改變,客戶信息總數加1。
-----------------------------
--從列表框選擇客戶--
組合框選中項:小龍女。
文本框顯示:小龍女。

  由於在本實例中不同的組件類(即不同的同事類)所擁有的方法並不完全相同,因此中介者類沒有針對抽象同事類編程,導致在具體中介者類中需要維持對具體同事類的引用,客戶端代碼無法完全透明地對待所有同事類和中介者類。在某些情況下,如果設計得當,可以在客戶端透明地對同事類和中介者類編程,這樣系統將具有更好的靈活性和可擴展性。

6、總結

  中介者模式將一個網狀的系統結構變成一個以中介者對象爲中心的星形結構,在這個星型結構中,使用中介者對象與其他對象的一對多關係來取代原有對象之間的多對多關係。中介者模式在事件驅動類軟件中應用較爲廣泛,特別是基於GUI(Graphical User Interface,圖形用戶界面)的應用軟件,此外,在類與類之間存在錯綜複雜的關聯關係的系統中,中介者模式都能得到較好的應用。

6.1、優缺點

  中介者模式的主要優點如下:

  • (1) 中介者模式簡化了對象之間的交互,它用中介者和同事的一對多交互代替了原來同事之間的多對多交互,一對多關係更容易理解、維護和擴展,將原本難以理解的網狀結構轉換成相對簡單的星型結構。
  • (2) 中介者模式可將各同事對象解耦。中介者有利於各同事之間的松耦合,我們可以獨立的改變和複用每一個同事和中介者,增加新的中介者和新的同事類都比較方便,更好地符合“開閉原則”。
  • (3) 可以減少子類生成,中介者將原本分佈於多個對象間的行爲集中在一起,改變這些行爲只需生成新的中介者子類即可,這使各個同事類可被重用,無須對同事類進行擴展。

  中介者模式的主要缺點如下:

  • 在具體中介者類中包含了大量同事之間的交互細節,可能會導致具體中介者類非常複雜,使得系統難以維護

6.2、適用場景

  在以下情況下可以考慮使用中介者模式:

  • (1) 系統中對象之間存在複雜的引用關係,系統結構混亂且難以理解。
  • (2) 一個對象由於引用了其他很多對象並且直接和這些對象通信,導致難以複用該對象。
  • (3) 想通過一箇中間類來封裝多個類中的行爲,而又不想生成太多的子類。可以通過引入中介者類來實現,在中介者中定義對象交互的公共行爲,如果需要改變行爲則可以增加新的具體中介者類。

後記

  設計模式部分參考設計模式(劉偉).pdf,作者博客地址:https://blog.csdn.net/LoveLion
  本項目爲參考某馬視頻開發,相關視頻及配套資料可自行度娘或者聯繫本人。上面爲自己編寫的開發文檔,持續更新。歡迎交流,本人QQ:806797785

前端項目源代碼地址:https://gitee.com/gaogzhen/vue-leyou
後端JAVA源代碼地址:https://gitee.com/gaogzhen/JAVA
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章