「每天一個知識點」精講設計模式之中介者模式

點擊上方"java全棧技術"關注,每天學習一個java知識點

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

「每天一個知識點」精講設計模式之中介者模式

圖1 QQ聊天示意圖

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

1 客戶信息管理窗口的初始設計

Sunny軟件公司欲開發一套CRM系統,其中包含一個客戶信息管理模塊,所設計的“客戶信息管理窗口”界面效果圖如圖2所示:

「每天一個知識點」精講設計模式之中介者模式

圖2 “客戶信息管理窗口”界面圖

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

如果實現界面組件之間的交互是Sunny公司開發人員必須面對的一個問題?

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

(1) 當用戶單擊“增加”按鈕、“刪除”按鈕、“修改”按鈕或“查詢”按鈕時,界面左側的“客戶選擇組合框”、“客戶列表”以及界面中的文本框將產生響應。

(2) 當用戶通過“客戶選擇組合框”選中某個客戶姓名時,“客戶列表”和文本框將產生響應。

(3) 當用戶通過“客戶列表”選中某個客戶姓名時,“客戶選擇組合框”和文本框將產生響應。

於是,Sunny公司開發人員根據組件之間的交互關係繪製瞭如圖3所示初始類圖:

「每天一個知識點」精講設計模式之中介者模式

圖3 “客戶信息管理窗口”原始類圖

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

「每天一個知識點」精講設計模式之中介者模式

「每天一個知識點」精講設計模式之中介者模式

「每天一個知識點」精講設計模式之中介者模式

「每天一個知識點」精講設計模式之中介者模式

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

(1) 系統結構複雜且耦合度高:每一個界面組件都與多個其他組件之間產生相互關聯和調用,若一個界面組件對象發生變化,需要跟蹤與之有關聯的其他所有組件並進行處理,系統組件之間呈現一種較為複雜的網狀結構,組件之間的耦合度高。

(2) 組件的可重用性差:由於每一個組件和其他組件之間都具有很強的關聯,若沒有其他組件的支持,一個組件很難被另一個系統或模塊重用,這些組件表現出來更像一個不可分割的整體,而在實際使用時,我們往往需要每一個組件都能夠單獨重用,而不是重用一個由多個組件組成的複雜結構。

(3) 系統的可擴展性差:如果在上述系統中增加一個新的組件類,則必須修改與之交互的其他組件類的源代碼,將導致多個類的源代碼需要修改,同樣,如果要刪除一個組件也存在類似的問題,這違反了“開閉原則”,可擴展性和靈活性欠佳。

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

2 中介者模式概述

如果在一個系統中對象之間的聯繫呈現為網狀結構,如圖4所示。對象之間存在大量的多對多聯繫,將導致系統非常複雜,這些對象既會影響別的對象,也會被別的對象所影響,這些對象稱為同事對象,它們之間通過彼此的相互作用實現系統的行為。在網狀結構中,幾乎每個對象都需要與其他對象發生相互作用,而這種相互作用表現為一個對象與另外一個對象的直接耦合,這將導致一個過度耦合的系統。

「每天一個知識點」精講設計模式之中介者模式

圖4 對象之間存在複雜關係的網狀結構

中介者模式可以使對象之間的關係數量急劇減少,通過引入中介者對象,可以將系統的網狀結構變成以中介者為中心的星形結構,如圖5所示。在這個星形結構中,同事對象不再直接與另一個對象聯繫,它通過中介者對象與另一個對象發生相互作用。中介者對象的存在保證了對象結構上的穩定,也就是說,系統的結構不會因為新對象的引入帶來大量的修改工作。

「每天一個知識點」精講設計模式之中介者模式

圖5 引入中介者對象的星型結構

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

中介者模式定義如下:

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

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

「每天一個知識點」精講設計模式之中介者模式

圖6 中介者模式結構圖

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

Mediator(抽象中介者):它定義一個接口,該接口用於與各同事對象之間進行通信。

ConcreteMediator(具體中介者):

它是抽象中介者的子類,通過協調各個同事對象來實現協作行為,它維持了對各個同事對象的引用。

Colleague(抽象同事類):它定義各個同事類公有的方法,並聲明瞭一些抽象方法來供子類實現,同時它維持了一個對抽象中介者類的引用,其子類可以通過該引用來與中介者通信。

ConcreteColleague(具體同事類):它是抽象同事類的子類;每一個同事對象在需要和其他同事對象通信時,先與中介者通信,通過中介者來間接完成與其他同事類的通信;在具體同事類中實現了在抽象同事類中聲明的抽象方法。

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

(1) 中轉作用(結構性):通過中介者提供的中轉作用,各個同事對象就不再需要顯式引用其他同事,當需要和其他同事進行通信時,可通過中介者來實現間接調用。該中轉作用屬於中介者在結構上的支持。

(2) 協調作用(行為性):中介者可以更進一步的對同事之間的關係進行封裝,同事可以一致的和中介者進行交互,而不需要指明中介者需要具體怎麼做,中介者根據封裝在自身內部的協調邏輯,對同事的請求進行進一步處理,將同事成員之間的關係行為進行分離和封裝。該協調作用屬於中介者在行為上的支持。

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

「每天一個知識點」精講設計模式之中介者模式

在抽象中介者中可以定義一個同事類的集合,用於存儲同事對象並提供註冊方法,同時聲明瞭具體中介者類所具有的方法。在具體中介者類中將實現這些抽象方法,典型的具體中介者類代碼如下所示:

「每天一個知識點」精講設計模式之中介者模式

在具體中介者類中將調用同事類的方法,調用時可以增加一些自己的業務代碼對調用進行控制。

在抽象同事類中維持了一個抽象中介者的引用,用於調用中介者的方法,典型的抽象同事類代碼如下所示:

「每天一個知識點」精講設計模式之中介者模式

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

「每天一個知識點」精講設計模式之中介者模式

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

思考

如何理解同事類中的自身方法與依賴方法?

3 完整解決方案

為了協調界面組件對象之間的複雜交互關係,Sunny公司開發人員使用中介者模式來設計客戶信息管理窗口,其結構示意圖如圖7所示:

「每天一個知識點」精講設計模式之中介者模式

圖7 引入了中介者類的“客戶信息管理窗口”結構示意圖

圖7只是一個重構之後的結構示意圖,在具體實現時,為了確保系統具有更好的靈活性和可擴展性,我們需要定義抽象中介者和抽象組件類,其中抽象組件類是所有具體組件類的公共父類,完整類圖如圖20-8所示:

「每天一個知識點」精講設計模式之中介者模式

圖8 重構後的“客戶信息管理窗口”結構圖

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

「每天一個知識點」精講設計模式之中介者模式

「每天一個知識點」精講設計模式之中介者模式

「每天一個知識點」精講設計模式之中介者模式

「每天一個知識點」精講設計模式之中介者模式

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

「每天一個知識點」精講設計模式之中介者模式

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

--單擊增加按鈕--

列表框增加一項:張無忌。

組合框增加一項:張無忌。

客戶信息增加成功後文本框清空。

-----------------------------

--從列表框選擇客戶--

組合框選中項:小龍女。

文本框顯示:小龍女。

4 中介者與同事類的擴展

Sunny軟件公司CRM系統的客戶對“客戶信息管理窗口”提出了一個修改意見:要求在窗口的下端能夠及時顯示當前系統中客戶信息的總數。修改之後的界面如圖9所示:

「每天一個知識點」精講設計模式之中介者模式

圖9 修改之後的“客戶信息管理窗口”界面圖

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

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

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

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

引入Label之後“客戶信息管理窗口”類結構示意圖如圖10所示:

「每天一個知識點」精講設計模式之中介者模式

圖10 增加Label組件類後的“客戶信息管理窗口”結構示意圖

由於【解決方案二】無須修改ConcreteMediator類,更符合“開閉原則”,因此我們選擇該解決方案來對新增Label類進行處理,對應的完整類圖如圖11所示:

「每天一個知識點」精講設計模式之中介者模式

圖11 修改之後的“客戶信息管理窗口”結構圖

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

「每天一個知識點」精講設計模式之中介者模式

「每天一個知識點」精講設計模式之中介者模式

修改客戶端測試代碼:

「每天一個知識點」精講設計模式之中介者模式

「每天一個知識點」精講設計模式之中介者模式

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

--單擊增加按鈕--

列表框增加一項:張無忌。

組合框增加一項:張無忌。

客戶信息增加成功後文本框清空。

文本標籤內容改變,客戶信息總數加1。

-----------------------------

--從列表框選擇客戶--

組合框選中項:小龍女。

文本框顯示:小龍女。

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

思考

如果不使用中介者模式,按照圖3所示設計方案,增加新組件時原有系統該如何修改?

在中介者模式的實際使用過程中,如果需要引入新的具體同事類,只需要繼承抽象同事類並實現其中的方法即可,由於具體同事類之間並無直接的引用關係,因此原有所有同事類無須進行任何修改,它們與新增同事對象之間的交互可以通過修改或者增加具體中介者類來實現

如果需要在原有系統中增加新的具體中介者類,只需要繼承抽象中介者類(或已有的具體中介者類)並覆蓋其中定義的方法即可,在新的具體中介者中可以通過不同的方式來處理對象之間的交互,也可以增加對新增同事的引用和調用。在客戶端中只需要修改少許代碼(如果引入配置文件的話有時可以不修改任何代碼)就可以實現中介者的更換。

5 中介者模式總結

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

1. 主要優點

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

(1) 中介者模式

簡化了對象之間的交互,它用中介者和同事的一對多交互代替了原來同事之間的多對多交互,一對多關係更容易理解、維護和擴展,將原本難以理解的網狀結構轉換成相對簡單的星型結構。

(2) 中介者模式可將各同事對象解耦。中介者有利於各同事之間的松耦合,我們可以獨立的改變和複用每一個同事和中介者,增加新的中介者和新的同事類都比較方便,更好地符合“開閉原則”。

(3) 可以減少子類生成,中介者將原本分佈於多個對象間的行為集中在一起,改變這些行為只需生成新的中介者子類即可,這使各個同事類可被重用,無須對同事類進行擴展。

2. 主要缺點

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

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

3. 適用場景

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

(1) 系統中對象之間存在複雜的引用關係,系統結構混亂且難以理解。

(2) 一個對象由於引用了其他很多對象並且直接和這些對象通信,導致難以複用該對象

(3) 想通過一箇中間類來封裝多個類中的行為,而又不想生成太多的子類。可以通過引入中介者類來實現,在中介者中定義對象交互的公共行為,如果需要改變行為則可以增加新的具體中介者類。

原文:http://blog.csdn.net/lovelion


分享到:


相關文章: