我們都用過QQ,它有兩種聊天方式:一是私聊,二是群聊。使用QQ群,一個使用者就可以向多個使用者傳送相同的資訊和檔案,從而無需一一傳送,節省大量時間。通過引入群的機制,極大地減少系統中使用者之間的兩兩通訊,使用者與使用者之間的聯絡可以通過群的機制來實現。
在有些軟體中,某些類/物件之間的相互呼叫關係錯綜複雜,類似於QQ使用者之間的關係,此時,特別需要一個類似“QQ群”一樣的中間類來協調這些類/物件之間的複雜關係,以降低系統的耦合度。因此,一個設計模式因此誕生,它就是中介者模式。
中介者模式(Mediator) | 學習難度:★★★☆☆ | 使用頻率:★★☆☆☆ |
一、客戶資訊管理模組的初始設計
1.1 需求背景
Background:M公司欲開發一套CRM系統,其中包含一個客戶資訊管理模組,所涉及的“客戶資訊管理視窗”介面效果圖如下圖所示:
M公司開發人員通過分析發現,在上圖中,介面元件之間存在較為複雜的互動關係:如果刪除一個客戶,則將從客戶列表中刪掉對應的項,客戶選擇組合框中客戶名稱也稱將減少一個;如果增加一個客戶資訊,則客戶列表中將增加一個客戶,且組合框中也將增加一項。
1.2 初始設計
M公司開發人員針對元件之間的互動關係進行了分析,發現:
(1)當使用者單擊“增加”、“刪除”、“修改”或“查詢”時,介面左側的“客戶選擇組合框”、“客戶列表”以及介面中的文字框將產生響應。
(2)當使用者通過”客戶選擇組合框“選中某個客戶姓名時,”客戶列表“和文字框將產生響應。
(3)當使用者通過“客戶列表”選中某個客戶姓名時,“客戶選擇組合框”和文字框將產生響應。
根據互動關係,開發人員繪製瞭如下圖所示的初始類圖。
不難發現,上述設計存在以下問題:
(1)系統結構負責且耦合度高 => 複雜的網狀結構,OMG!
(2)元件的可重用性差 => 由於每一個元件和其他元件之間都具有很強的關聯,很難重用!
(3)系統的擴充套件性差 => 如果在上述系統中增加一個新的元件類,必須修改與之互動的各個元件原始碼!
二、中介者模式概述
2.1 中介者模式簡介
如果在一個系統中物件之間存在多對多的相互關係,可以將物件之間的一些互動行為從各個物件中分離出來,並集中封裝在一箇中介者物件中,並由該中介者進行統一協調,這樣物件之間多對多的複雜關係就轉化為了相對簡單的一對多關係。通過引入中介者來簡化物件之間的複雜關係,它是迪米特法則的一個典型應用。
中介者(Mediator)模式:用一箇中介物件來封裝一系列的物件互動,中介者使各物件不需要顯示地相互引用,從而使其耦合鬆散,而且可以相對獨立地改變它們之間的互動。中介者模式又稱為調停模式,它是一種物件行為型模式。
2.2 中介者模式結構
在中介者模式中,引入了用於協調其他物件/類之間的相互呼叫的中介者類,為了讓系統具有更好的靈活性和可擴充套件性,通常還提供了抽象中介者,其結構圖如下圖所示:
中介者模式結構圖中主要包含以下4個角色:
(1)Mediator(抽象中介者):它定義了一個介面,該介面用於與各同事物件之間進行通訊。
(2)ConcreteMediator(具體中介者):它實現了介面,通過協調各個同事物件來實現協作行為,維持了各個同事物件的引用。
(3)Colleague(抽象同事類):它定義了各個同事類公有的方法,並宣告瞭一些抽象方法來供子類實現,同時維持了一個對抽象中介者類的引用,其子類可以通過該引用來與中介者通訊。
(4)ConcreteColleague(具體同事類):抽象同事類的子類,每一個同事物件需要和其他物件通訊時,都需要先與中介者物件通訊,通過中介者來間接完成與其他同事類的通訊。
三、客戶資訊管理模組的重構實現
3.1 重構後的設計結構
M公司開發人員藉助中介者模式來重新設計結構如下圖所示:
在具體實現時,為了確保系統有更好的靈活性和可擴充套件性,需要定義抽象中介者和抽象元件類,其中抽象元件類是所有具體元件類的公共父類,完整類圖如下圖所示:
其中,Component充當抽象同事類,Button,List,ComboBox和TextBox充當具體同事類,Mediator充當抽象中介者類,ConcreteMediator充當具體中介類。在ConcreteMediator中維持了對具體同事物件的引用,為了簡化ConcreteMediator類的程式碼,在其中只定義了一個Button物件和TextBox物件。
3.2 重構後的程式碼實現
(1)抽象中介者:Mediator
/// <summary> /// 抽象中介者 /// </summary> public abstract class Mediator { public abstract void ComponenetChanged(Component c); }
(2)具體中介者:ConcreteMediator
/// <summary> /// 具體中介者 /// </summary> public class ConcreteMediator : Mediator { // 維持對各個同事物件的引用 public Button addButton; public List list; public TextBox userNameTextBox; public ComboBox cb; // 封裝同事物件之間的互動 public override void ComponenetChanged(Component c) { // 單擊按鈕 if (c == addButton) { Console.WriteLine("-- 單擊增加按鈕 --"); list.Update(); cb.Update(); userNameTextBox.Update(); } // 從列表框選擇客戶 else if (c == list) { Console.WriteLine("-- 從列表框選擇客戶 --"); cb.Select(); userNameTextBox.SetText(); } // 從組合框選擇客戶 else if (c == cb) { Console.WriteLine("-- 從組合框選擇客戶 --"); cb.Select(); userNameTextBox.SetText(); } } }
(3)抽象同事類:Colleague
/// <summary> /// 抽象同事類:抽象元件 /// </summary> public abstract class Component { protected Mediator mediator; public void SetMediator(Mediator mediator) { this.mediator = mediator; } // 轉發呼叫 public void Changed() { mediator.ComponenetChanged(this); } public abstract void Update(); }
(4)具體同事類:ConcreteColleague
/// <summary> /// 具體同事類:按鈕元件 /// </summary> public class Button : Component { public override void Update() { // 按鈕不產生響應 } } /// <summary> /// 具體同事類:列表框元件 /// </summary> public class List : Component { public override void Update() { Console.WriteLine("列表框增加一項:張無忌"); } public void Select() { Console.WriteLine("列表框選中項:小龍女"); } } /// <summary> /// 具體同事類:組合框元件 /// </summary> public class ComboBox : Component { public override void Update() { Console.WriteLine("組合框增加一項:張無忌"); } public void Select() { Console.WriteLine("組合框選中項:小龍女"); } } /// <summary> /// 具體同事類:文字框元件 /// </summary> public class TextBox : Component { public override void Update() { Console.WriteLine("客戶資訊增加成功後文字框清空"); } public void SetText() { Console.WriteLine("文字框顯示:小龍女"); } } /// <summary> /// 具體同事類:標籤元件 /// </summary> public class Label : Component { public override void Update() { Console.WriteLine("文字標籤內容改變,客戶資訊總數量加1"); } }
(5)客戶端測試:
public class Program { public static void Main() { // Step1.定義中介者物件 ConcreteMediator mediator = new ConcreteMediator(); // Step2.定義同事物件 Button addButton = new Button(); List list = new List(); ComboBox cb = new ComboBox(); TextBox userNameTextBox = new TextBox(); addButton.SetMediator(mediator); list.SetMediator(mediator); cb.SetMediator(mediator); userNameTextBox.SetMediator(mediator); mediator.addButton = addButton; mediator.list = list; mediator.cb = cb; mediator.userNameTextBox = userNameTextBox; // Step3.點選增加按鈕 addButton.Changed(); Console.WriteLine("---------------------------------------------"); // Step4.從列表框選擇客戶 list.Changed(); } }
除錯執行後的結果如下所示:
四、中介者模式小結
4.1 主要優點
(1)簡化了物件之間的互動,它用中介者和同事的一對多互動替代了原來同事之間的多對多互動。
(2)將各同事物件解耦,可以獨立地改變和複用每個同事和中介者,增加新的中介和同事很方便,符合開閉原則。
(3)可以減少大量同事子類的生成,改變同事行為只需要生成新的中介者子類即可。
4.2 主要缺點
具體中介者子類中包含了大量的同事之間的互動細節,可能會導致具體中介者類非常複雜,使得系統難以維護。
4.3 應用場景
(1)系統中物件之間存在複雜的引用關係 => 系統結構混亂且難以理解
(2)一個物件由於引用了其他很多物件並且直接和這些物件通訊 => 難以複用該物件
(3)想要通過一箇中間類來封裝多個類的行為又不想生成太多子類 => 引入中介者即可實現
參考資料
(1)劉偉,《設計模式的藝術—軟體開發人員內功修煉之道》