【白話設計模式十三】中介者模式(Mediator)

#0 系列目錄#編程

#1 場景問題# ##1.1 若是沒有主板## 你們都知道,電腦裏面各個配件之間的交互,主要是經過主板來完成的(事實上主板有不少的功能,這裏不去討論)。試想一下,若是電腦裏面沒有主板,會怎樣呢?設計模式

若是電腦裏面沒有了主板,那麼各個配件之間就必須自行相互交互,以互相傳送數據,理論上說,基本上各個配件相互之間都存在交互數據的可能。如圖10.1所示:ide

輸入圖片說明

這也太複雜了吧,這還沒完呢,因爲各個配件的接口不一樣,那麼相互之間交互的時候,還必須把數據接口進行轉換才能匹配上,那就更恐怖了測試

所幸是有了主板,各個配件的交互徹底經過主板來完成,每一個配件都只須要和主板交互,而主板知道如何和全部的配件打交道,那就簡單多了,這就避免瞭如圖10.1所描述的那樣亂做一團,有主板後的結構如圖10.2所示:ui

輸入圖片說明

##1.2 有何問題## 若是上面的狀況發生在軟件開發上呢?this

若是把每一個電腦配件都抽象成爲一個類或者是子系統,那就至關於出現了多個類之間相互交互,並且交互還很繁瑣,致使每一個類都必須知道全部須要交互的類,也就是咱們常說的類和類耦合了,是否是很麻煩?.net

在軟件開發中出現這種狀況可就不妙了,不但開發的時候每一個類會複雜,由於要兼顧其它的類,更要命的是每一個類在發生改動的時候,須要通知全部相關的類一塊兒修改,由於接口或者是功能發生了變更,使用它的地方都得變,快要瘋了吧!設計

那該如何來簡化這種多個對象之間的交互呢?代理

##1.3 使用電腦來看電影## 爲了演示,考慮一個稍微具體點的功能。在平常生活中,咱們常用電腦來看電影,把這個過程描述出來,這裏僅僅考慮正常的狀況,也就是有主板的狀況,簡化後假定會有以下的交互過程:code

首先是光驅要讀取光盤上的數據,而後告訴主板,它的狀態改變了;

主板去獲得光驅的數據,把這些數據交給CPU進行分析處理;

CPU處理完後,把數據分紅了視頻數據和音頻數據,通知主板,它處理完了;

主板去獲得CPU處理事後的數據,分別把數據交給顯卡和聲卡,去顯示出視頻和發出聲音;

固然這是一個持續的、不斷重複的過程,從而造成不間斷的視頻和聲音,具體的運行過程不在討論之列,假設就有如上簡單的交互關係就能夠了。也就是說想看電影,把光盤放入光驅,光驅開始讀盤,就能夠看電影了。

如今要求使用程序把這個過程描述出來,該如何具體實現呢?

#2 解決方案# ##2.1 中介者模式來解決## 用來解決上述問題的一個合理的解決方案就是中介者模式。那麼什麼是中介者模式呢?

  1. 中介者模式定義

輸入圖片說明

  1. 應用中介者模式來解決的思路

仔細分析上面的問題,根本緣由就在於多個對象須要相互交互,從而致使對象之間緊密耦合,這就不利於對象的修改和維護。

中介者模式的解決思路很簡單,跟電腦的例子同樣,中介者模式經過引入一箇中介對象,讓其它的對象都只和中介對象交互,而中介對象知道如何和其它全部的對象交互,這樣對象之間的交互關係就沒有了,從而實現對象之間的解耦。

對於中介對象而言,全部相互交互的對象,被視爲同事類,中介對象就是來維護各個同事之間的關係,而全部的同事類都只是和中介對象交互。

每一個同事對象,當本身發生變化的時候,不須要知道這會引發其它對象有什麼變化,它只須要通知中介者就能夠了,而後由中介者去與其它對象交互。這樣鬆散耦合帶來的好處是,除了讓同事對象之間相互沒有關聯外,還有利於功能的修改和擴展。

有了中介者事後,全部的交互都封裝到中介者對象裏面,各個對象就再也不須要維護這些關係了。擴展關係的時候也只須要擴展或修改中介者對象就能夠了

##2.2 模式結構和說明## 中介者模式的結構如圖10.3所示:

輸入圖片說明

Mediator:中介者接口。在裏面定義各個同事之間交互須要的方法,能夠是公共的通信方法,好比changed方法,你們都用,也能夠是小範圍的交互方法。

ConcreteMediator:具體中介者實現對象。它須要瞭解並維護各個同事對象,並負責具體的協調各同事對象的交互關係

Colleague:同事類的定義,一般實現成爲抽象類,主要負責約束同事對象的類型,並實現一些具體同事類之間的公共功能,好比:每一個具體同事類都應該知道中介者對象,也就是具體同事類都會持有中介者對象,就能夠定義到這個類裏面。

ConcreteColleague:具體的同事類,實現本身的業務,在須要與其它同事通信的時候,就與持有的中介者通訊,中介者會負責與其它的同事交互

##2.3 中介者模式示例代碼##

  1. 先來看看全部同事的父類的定義

按照前面的描述,全部須要交互的對象,都被視爲同事類,這些同事類應該有一個統一的約束。並且全部的同事類都須要和中介者對象交互,換句話說就是全部的同事都應該持有中介者對象。

所以,爲了統一約束衆多的同事類,併爲同事類提供持有中介者對象的公共功能,先來定義一個抽象的同事類,在裏面實現持有中介者對象的公共功能。

要提醒一點,下面示例的這個抽象類是沒有定義抽象方法的,主要是用來約束全部同事類的類型,示例代碼以下:

/**
 * 同事類的抽象父類
 */
public abstract class Colleague {
    /**
     * 持有中介者對象,每個同事類都知道它的中介者對象
     */
    private Mediator mediator;
    /**
     * 構造方法,傳入中介者對象
     * @param mediator 中介者對象
     */
    public Colleague(Mediator mediator) {
       this.mediator = mediator;
    }
    /**
     * 獲取當前同事類對應的中介者對象
     * @return 對應的中介者對象
     */
    public Mediator getMediator() {
       return mediator;
    }
}
  1. 接下來看看具體的同事類,在示意中它們的實現是差很少的,示例代碼以下:
/**
 * 具體的同事類A
 */
public class ConcreteColleagueA extends Colleague {
    public ConcreteColleagueA(Mediator mediator) {
       super(mediator);
    }
    /**
     * 示意方法,執行某些業務功能
     */
    public void someOperation() {
       //在須要跟其它同事通訊的時候,通知中介者對象
       getMediator().changed(this);
    }
}

/**
 * 具體的同事類B
 */
public class ConcreteColleagueB extends Colleague {
    public ConcreteColleagueB(Mediator mediator) {
       super(mediator);
    }
    /**
     * 示意方法,執行某些業務功能
     */
    public void someOperation() {
       //在須要跟其它同事通訊的時候,通知中介者對象
       getMediator().changed(this);
    }
}
  1. 接下來看看中介者的定義,示例代碼以下:
/**
 * 中介者,定義各個同事對象通訊的接口
 */
public interface Mediator {
    /**
     * 同事對象在自身改變的時候來通知中介者的方法,
     * 讓中介者去負責相應的與其它同事對象的交互
     * @param colleague 同事對象自身,好讓中介者對象經過對象實例去獲取同事對象的狀態
     */
    public void changed(Colleague colleague);
}
  1. 接下來看看具體的中介者實現,示例代碼以下:
/**
 * 具體的中介者實現
 */
public class ConcreteMediator implements Mediator {
    /**
     * 持有並維護同事A
     */
    private ConcreteColleagueA colleagueA;
    /**
     * 持有並維護同事B
     */
    private ConcreteColleagueB colleagueB;
  
    /**
     * 設置中介者須要瞭解並維護的同事A對象
     * @param colleague 同事A對象
     */
    public void setConcreteColleagueA(ConcreteColleagueA colleague) {
       colleagueA = colleague;
    }
    /**
     * 設置中介者須要瞭解並維護的同事B對象
     * @param colleague 同事B對象
     */
    public void setConcreteColleagueB(ConcreteColleagueB colleague) {
       colleagueB = colleague;
    }  

    public void changed(Colleague colleague) {
       //某個同事類發生了變化,一般須要與其它同事交互
       //具體協調相應的同事對象來實現協做行爲
    }
}

##2.4 使用中介者模式來實現示例## 要使用中介者模式來實現示例,那就要區分出同事對象和中介者對象。很明顯,主板是做爲中介者,而光驅、CPU、聲卡、顯卡等配件,都是做爲同事對象。

根據中介者模式的知識,設計出示例的程序結構如圖10.4所示:

輸入圖片說明

  1. 先來看看全部同事的抽象父類的定義,跟標準的實現是差很少的,示例代碼以下:
public abstract class Colleague {
    private Mediator mediator;
    public Colleague(Mediator mediator) {
       this.mediator = mediator;
    }
    public Mediator getMediator() {
       return mediator;
    }
}
  1. 定義衆多的同事

定義好了同事的抽象父類,接下來就應該具體的實現這些同事類了,按照順序一個一個來,先看看光驅類吧,示例代碼以下:

/**
 * 光驅類,一個同事類
 */
public class CDDriver extends Colleague{
    public CDDriver(Mediator mediator) {
       super(mediator);
    }
    /**
     * 光驅讀取出來的數據
     */
    private String data = "";
    /**
     * 獲取光驅讀取出來的數據
     * @return 光驅讀取出來的數據
     */
    public String getData(){
       return this.data;
    }
    /**
     * 讀取光盤
     */
    public void readCD(){
       //逗號前是視頻顯示的數據,逗號後是聲音
       this.data = "設計模式,值得好好研究";
       //通知主板,本身的狀態發生了改變
       this.getMediator().changed(this);
    }
}

/**
 * CPU類,一個同事類
 */
public class CPU extends Colleague{
    public CPU(Mediator mediator) {
       super(mediator);
    }
    /**
     * 分解出來的視頻數據
     */
    private String videoData = "";
    /**
     * 分解出來的聲音數據
     */
    private String soundData = "";
    /**
     * 獲取分解出來的視頻數據
     * @return 分解出來的視頻數據
     */
    public String getVideoData() {
       return videoData;
    }
    /**
     * 獲取分解出來的聲音數據
     * @return 分解出來的聲音數據
     */
    public String getSoundData() {
       return soundData;
    }
    /**
     * 處理數據,把數據分紅音頻和視頻的數據
     * @param data 被處理的數據
     */
    public void executeData(String data){
       //把數據分解開,前面的是視頻數據,後面的是音頻數據
       String [] ss = data.split(",");
       this.videoData = ss[0];
       this.soundData = ss[1];
       //通知主板,CPU的工做完成
       this.getMediator().changed(this);
    }
}

/**
 * 顯卡類,一個同事類
 */
public class VideoCard extends Colleague{
    public VideoCard(Mediator mediator) {
       super(mediator);
    }
    /**
     * 顯示視頻數據
     * @param data 被顯示的數據
     */
    public void showData(String data){
       System.out.println("您正觀看的是:"+data);
    }
}

/**
 * 聲卡類,一個同事類
 */
public class SoundCard extends Colleague{
    public SoundCard(Mediator mediator) {
       super(mediator);
    }
    /**
     * 按照聲頻數據發出聲音
     * @param data 發出聲音的數據
     */
    public void soundData(String data){
       System.out.println("畫外音:"+data);
    }
}
  1. 定義中介者接口

因爲全部的同事對象都要和中介者交互,來定義出中介者的接口,功能很少,提供一個讓同事對象在自身改變的時候來通知中介者的方法,示例代碼以下:

/**
 * 中介者對象的接口
 */
public interface Mediator {
    /**
     * 同事對象在自身改變的時候來通知中介者的方法,
     * 讓中介者去負責相應的與其它同事對象的交互
     * @param colleague 同事對象自身,好讓中介者對象經過對象實例
     *                  去獲取同事對象的狀態
     */
    public void changed(Colleague colleague);
}
  1. 實現中介者對象

中介者的功能就稍微多一點,它須要處理全部的同事對象之間的交互,好在咱們要示例的東西並不麻煩,僅有兩個功能處理而已,示例代碼以下:

/**
 * 主板類,實現中介者接口
 */
public class MotherBoard implements Mediator{
    /**
     * 須要知道要交互的同事類——光驅類
     */
    private CDDriver cdDriver = null;
    /**
     * 須要知道要交互的同事類——CPU類
     */
    private CPU cpu = null;
    /**
     * 須要知道要交互的同事類——顯卡類
     */
    private VideoCard videoCard = null;
    /**
     * 須要知道要交互的同事類——聲卡類
     */
    private SoundCard soundCard = null;

    public void setCdDriver(CDDriver cdDriver) {
       this.cdDriver = cdDriver;
    }
    public void setCpu(CPU cpu) {
       this.cpu = cpu;
    }
    public void setVideoCard(VideoCard videoCard) {
       this.videoCard = videoCard;
    }
    public void setSoundCard(SoundCard soundCard) {
       this.soundCard = soundCard;
    }
    public void changed(Colleague colleague) {
       if(colleague == cdDriver){
           //表示光驅讀取數據了
           this.opeCDDriverReadData((CDDriver)colleague);
       }else if(colleague == cpu){
           //表示CPU處理完了
           this.opeCPU((CPU)colleague);
       }
    }
    /**
     * 處理光驅讀取數據事後與其它對象的交互
     * @param cd 光驅同事對象
     */
    private void opeCDDriverReadData(CDDriver cd){
       //1:先獲取光驅讀取的數據
       String data = cd.getData();
       //2:把這些數據傳遞給CPU進行處理
       this.cpu.executeData(data);
    }
    /**
     * 處理CPU處理完數據後與其它對象的交互
     * @param cpu CPU同事類
     */
    private void opeCPU(CPU cpu){
       //1:先獲取CPU處理事後的數據
       String videoData = cpu.getVideoData();
       String soundData = cpu.getSoundData();
       //2:把這些數據傳遞給顯卡和聲卡展現出來
       this.videoCard.showData(videoData);
       this.soundCard.soundData(soundData);
    }
}
  1. 看個電影享受一下

定義完了同事類,也實現處理了它們交互的中介者對象,該來寫個客戶端使用它們,來看個電影,好好享受一下。寫個客戶端測試一下,看看效果,示例代碼以下:

public class Client {
    public static void main(String[] args) {
       //1:建立中介者——主板對象
       MotherBoard mediator = new MotherBoard();
       //2:建立同事類
       CDDriver cd = new CDDriver(mediator);
       CPU cpu = new CPU(mediator);
       VideoCard vc = new VideoCard(mediator);
       SoundCard sc = new SoundCard(mediator);

       //3:讓中介者知道全部的同事
       mediator.setCdDriver(cd);
       mediator.setCpu(cpu);
       mediator.setVideoCard(vc);
       mediator.setSoundCard(sc);
     
       //4:開始看電影,把光盤放入光驅,光驅開始讀盤
       cd.readCD();
    }
}

運行結果以下:

您正觀看的是:設計模式
畫外音:值得好好研究

如同上面的示例,對於光驅對象、CPU對象、顯卡對象和聲卡對象,須要相互交互,雖然只是簡單演示,可是也能看出來,它們的交互是比較麻煩的,因而定義一箇中介者對象——主板對象,來維護它們之間的交互關係,從而使得這些對象鬆散耦合

若是這個時候須要修改它們的交互關係,直接到中介者裏面修改就行了,也就是說它們的關係已經獨立封裝到中介者對象裏面了,能夠獨立的改變它們之間的交互關係,而不用去修改這些同事對象。

#3 模式講解# ##3.1 認識中介者模式##

  1. 模式的功能

中介者的功能很是簡單,就是封裝對象之間的交互。若是一個對象的操做會引發其它相關對象的變化,或者是某個操做須要引發其它對象的後續或連帶操做,而這個對象又不但願本身來處理這些關係,那麼就能夠找中介者,把全部的麻煩扔給它,只在須要的時候通知中介者,其它的就讓中介者去處理就能夠了。

反過來,其它的對象在操做的時候,可能會引發這個對象的變化,也能夠這麼作。最後對象之間就徹底分離了,誰都不直接跟其它對象交互,那麼相互的關係,所有被集中到中介者對象裏面了,全部的對象就只是跟中介者對象進行通訊,相互之間再也不有聯繫。

把全部對象之間的交互都封裝在中介者當中,無形中還獲得另一個好處,就是可以集中的控制這些對象的交互關係,這樣有什麼變化的時候,修改起來就很方便

  1. 須要Mediator接口嗎

要回答這個問題,先要搞清楚一件事情,接口用來幹什麼的?對,接口是用來實現「封裝隔離」的,那麼封裝誰?隔離誰呢?Mediator接口嘛,確定是用來封裝中介者對象的,使得使用中介者對象的客戶對象跟具體的中介者實現對象分離開。

瞭解了上面這些內容,回過來想一想,有沒有使用Mediator接口的必要,那就取決因而否會提供多個不一樣的中介者實現。若是中介者實現只有一個的話,並且預計中也沒有須要擴展的要求,那麼就能夠不定義Mediator接口,讓各個同事對象直接使用中介者實現對象;若是中介者實現不僅一個,或者預計中有擴展的要求,那麼就須要定義Mediator接口,讓各個同事對象來面向中介者接口編程,而無需關心具體的中介者實現

  1. 同事關係

在標準的中介者模式中,把使用中介者對象來交互的那些對象稱爲同事類,這不是亂叫的,在中介者模式中,要求這些類都要繼承相同的類,也就是說,這些對象從某個角度講是同一個類型,算是兄弟對象。

正是這些兄弟對象之間的交互關係很複雜,才產生了把這些交互關係分離出去,單獨作成中介者對象,這樣一來,這些兄弟對象就成了中介者對象眼裏的同事。

  1. 同事和中介者的關係

在中介者模式中,當一個同事對象發生了改變,須要主動通知中介者,讓中介者去處理與其它同事對象相關的交互。

這就致使了同事對象和中介者對象之間必須有關係,首先是同事對象須要知道中介者對象是誰;反過來,中介者對象也須要知道相關的同事對象,這樣它才能與同事對象進行交互。也就是說中介者對象和同事對象之間是相互依賴的。

  1. 如何實現同事和中介者的通訊

一個同事對象發生了改變,會通知中介者對象,中介者對象會處理與其它同事的交互,這就產生了同事對象和中介者對象的相互通訊。怎麼實現這種通訊關係呢?

一種實現方式是在Mediator接口中定義一個特殊的通知接口,做爲一個通用的方法,讓各個同事類來調用這個方法,在中介者模式結構圖裏畫的就是這種方式。在前面示例的也是這種方式,定義了一個通用的changed方法,而且把同事對象當作參數傳入,這樣在中介者對象裏面,就能夠去獲取這個同事對象的實例的數據了。

另一種實現方式是能夠採用觀察者模式,把Mediator實現成爲觀察者,而各個同事類實現成爲Subject,這樣同事類發生了改變,會通知Mediator。Mediator在接到通知事後,會與相應的同事對象進行交互。

  1. 中介者模式的調用順序示意圖

中介者模式的調用順序如圖10.5所示:

輸入圖片說明

##3.2 廣義中介者## 仔細查看中介者的結構、定義和示例,會發現幾個問題,使得中介者模式在實際使用的時候,變得繁瑣或困難。

  1. 其一:是否有必要爲同事對象定義一個公共的父類?

你們都知道,Java是單繼承的,爲了使用中介者模式,就讓這些同事對象繼承一個父類,這是很很差的;再說了,這個父類目前也沒有什麼特別的公共功能,也就是說繼承它也得不到多少好處。

在實際開發中,不少相互交互的對象自己是沒有公共父類的,強行加上一個父類,會讓這些對象實現起來特別彆扭。

  1. 其二:同事類有必要持有中介者對象嗎?

同事類須要知道中介者對象,以便當它們發生改變的時候,可以通知中介者對象,可是,是否須要做爲屬性,並經過構造方法傳入,這麼強的依賴關係呢?

也能夠有簡單的方式去通知中介對象,好比把中介對象作成單例,直接在同事類的方法裏面去調用中介者對象。

  1. 其三:是否須要中介者接口?

在實際開發中,很常見的狀況是不須要中介者接口的,並且中介者對象也不須要建立不少個實例,由於中介者是用來封裝和處理同事對象的關係的,它通常是沒有狀態須要維護的,所以中介者一般能夠實現成單例

  1. 其四:中介者對象是否須要持有全部的同事?

雖然說中介者對象須要知道全部的同事類,這樣中介者才能與它們交互。可是是否須要作爲屬性這麼強烈的依賴關係,並且中介者對象在不一樣的關係維護上,可能會須要不一樣的同事對象的實例,所以能夠在中介者處理的方法裏面去建立、或者獲取、或者從參數傳入須要的同事對象。

  1. 其五:中介者對象只是提供一個公共的方法,來接受同事對象的通知嗎?

從示例就能夠看出來,在公共方法裏,仍是要去區分究竟是誰調過來,這仍是簡單的,尚未去區分究竟是什麼樣的業務觸發調用過來的,由於不一樣的業務,引發的與其它對象的交互是不同的。

所以在實際開發中,一般會提供具體的業務通知方法,這樣就不用再去判斷究竟是什麼對象,具體是什麼業務了

基於上面的考慮,在實際應用開發中,常常會簡化中介者模式,來使開發變得簡單,好比有以下的簡化:

一般會去掉同事對象的父類,這樣可讓任意的對象,只要須要相互交互,就能夠成爲同事;

還有一般不定義Mediator接口,把具體的中介者對象實現成爲單例;

另一點就是同事對象再也不持有中介者,而是在須要的時候直接獲取中介者對象並調用;中介者也再也不持有同事對象,而是在具體處理方法裏面去建立、或者獲取、或者從參數傳入須要的同事對象。

把這樣通過簡化、變形使用的狀況稱爲廣義中介者。仍是來舉個實際點的例子看看吧。

  1. 部門與人員

幾乎在每一個應用系統中都須要這樣的功能模塊:部門管理、人員管理,爲了簡單點演示,把模塊簡化成類,也就是有一個部門類Dep和人員類User。

首先想一想部門類Dep和人員類User之間是什麼關係,一對一?一對多?仍是多對多?

可能在不一樣的系統裏面,根據須要會作成不一樣的關係,但從實際狀況講,部門和人員應該是多對多的,也就是一個部門能夠有多我的,而一我的也能夠加入多個部門。

對於一個部門有多我的,估計你們都能理解。而一我的也能夠加入多個部門,或許有些朋友就以爲有些問題了,由於在他們作系統的經驗上,是一我的只屬於一個部門的。

事實上一我的是能夠屬於多個部門的,好比:某人是開發部的經理,同時也是銷售部門的技術總監,爲銷售部門給客戶的解決方案中的技術部分進行把關,同時還能夠是客戶服務部門的技術顧問,爲他們解決客戶的技術問題提供指導。

好了,理解了部門和人員是多對多的關係事後,有些朋友可能會作出以下的設計,不就是個多對多嗎,類之間的多對多也很容易表達啊,以下:

public class Dep {
    private Collection<User> colUser = new ArrayList<User>();
}
public class User {
    private Collection<Dep> colDep = new ArrayList<Dep>();
}

很簡單,是吧,一個部門有多我的員,一我的員屬於多個部門。

  1. 問題的出現

真的這麼簡單嗎?再進一步想一想部門和人員的功能交互,就會知道這樣設計是存在問題的,舉幾個常見的功能:

部門被撤銷

部門之間進行合併

人員離職

人員從一個部門調職到另一個部門

想一想要實現這些功能,按照前面的設計,該怎麼作呢?

(1)系統運行期間,部門被撤銷了,就意味着這個部門不存在了,但是原來這個部門下全部的人員,每一個人員的所屬部門中都有這個部門呢,那麼就須要先通知全部的人員,把這個部門從它們的所屬部門中去掉,而後才能夠清除這個部門。

(2)部門合併,是合併成一個新的部門呢,仍是把一個部門併入到另外一個部門?若是是合併成一個新的部門,那麼須要把原有的兩個部門撤銷,而後再新增一個部門;若是是把一個部門合併到另外一個部門裏面,那就是撤銷掉一個部門,而後把這個部門下的人員移動到這個部門。無論是那種狀況,都面臨着須要通知相應的人員進行更改這樣的問題。

(3)人員離職了,反過來就須要通知他所屬於的部門,從部門的擁有人員的記錄中去除掉這我的員。

(4)人員調職,一樣須要通知相關的部門,先從原來的部門中去除掉,而後再到新的部門中添加上。

看了上述的描述,感受如何?是否是「煩就一個字」啊!

麻煩的根源在什麼地方呢?仔細想一想,對了,麻煩的根源就在於部門和人員之間的耦合,這樣致使操做人員的時候,須要操做全部相關的部門,而操做部門的時候又須要操做全部相關的人員,使得部門和人員攪和在了一塊兒

  1. 中介者來解決

找到了根源就好辦了,採用中介者模式,引入一箇中介者對象來管理部門和人員之間的關係,就能解決這些問題了

若是採用標準的中介者模式,想一想上面提出的那些問題點吧,就知道實現起來會很彆扭。所以採用廣義的中介者來解決,這樣部門和人員就徹底解耦了,也就是說部門不知道人員,人員也不知道部門,它們徹底分開,它們之間的關係就徹底由中介者對象來管理了。這個時候的結構如圖10.6所示:

輸入圖片說明

  1. 實現示例

(1)首先定義部門類,示例代碼以下:

/**
 * 部門類
 */
public class Dep{
    /**
     * 描述部門編號
     */
    private String depId;
    /**
     * 描述部門名稱
     */
    private String depName;

    public String getDepId() {
       return depId;
    }
    public void setDepId(String depId) {
       this.depId = depId;
    }
    public String getDepName() {
       return depName;
    }
    public void setDepName(String depName) {
       this.depName = depName;
    }
    /**
     * 撤銷部門
     * @return 是否撤銷成功
     */
    public boolean deleteDep(){
       //1:要先經過中介者去清除掉全部與這個部門相關的部門和人員的關係
       DepUserMediatorImpl mediator = DepUserMediatorImpl.getInstance();
       mediator.deleteDep(depId);
       //2:而後才能真的清除掉這個部門
       //請注意在實際開發中,這些業務功能可能會作到業務層去,
       //並且實際開發中對於已經使用的業務數據一般是不會被刪除的,
       //而是會被作爲歷史數據保留
       return true;
    }
}

(2)接下來定義人員類,示例代碼以下:

/**
 * 人員類
 */
public class User{
    /**
     * 人員編號
     */
    private String userId;
    /**
     * 人員名稱
     */
    private String userName;

    public String getUserId() {
       return userId;
    }
    public void setUserId(String userId) {
       this.userId = userId;
    }
    public String getUserName() {
       return userName;
    }
    public void setUserName(String userName) {
       this.userName = userName;
    }
    /**
     * 人員離職
     * @return 是否處理成功
     */
    public boolean dimission(){
       //1:要先經過中介者去清除掉全部與這我的員相關的部門和人員的關係
       DepUserMediatorImpl mediator = DepUserMediatorImpl.getInstance();
       mediator.deleteUser(userId);
       //2:而後才能真的清除掉這我的員
       //請注意,實際開發中,人員離職,是不會真的刪除人員記錄的,
       //一般是把人員記錄的狀態或者是刪除標記設置成已刪除,
       //只是再也不參加新的業務,可是已經發生的業務記錄是不會被清除掉的    

       return true;
    }
}

(3)順帶看一下描述部門和人員關係的對象,很是簡單,示例代碼以下:

/**
 *  描述部門和人員關係的類
 */
public class DepUserModel {
    /**
     * 用於部門和人員關係的編號,用作主鍵
     */
    private String depUserId;
    /**
     * 部門的編號
     */
    private String depId;
    /**
     * 人員的編號
     */
    private String userId;
}

(4)具體的中介者實現:

首先中介者要管理部門和人員的關係,因此在中介者實現裏面添加了一些測試的數據,爲此還專門作了一個用來描述部門和人員關係的數據對象;其次在中介者裏面只是實現了撤銷部門和人員離職相應的關係處理,其它的沒有實現;另外,這個中介者實現被實現成單例的了。示例代碼以下:

/**
 * 實現部門和人員交互的中介者實現類
 * 說明:爲了演示的簡潔性,只示例實現撤銷部門和人員離職的功能
 */
public class DepUserMediatorImpl{
    private static DepUserMediatorImpl mediator = new DepUserMediatorImpl();
    private DepUserMediatorImpl(){
       //調用初始化測試數據的功能
       initTestData();
    }
    public static DepUserMediatorImpl getInstance(){
       return mediator;
    }
  
    /**
     * 測試用,記錄部門和人員的關係
     */
    private Collection<DepUserModel> depUserCol = new ArrayList<DepUserModel>();
   
    /**
     * 初始化測試數據
     */
    private void initTestData(){
       //準備一些測試數據
       DepUserModel du1 = new DepUserModel();
       du1.setDepUserId("du1");
       du1.setDepId("d1");
       du1.setUserId("u1");    
       depUserCol.add(du1);
     
       DepUserModel du2 = new DepUserModel();
       du2.setDepUserId("du2");
       du2.setDepId("d1");
       du2.setUserId("u2");    
       depUserCol.add(du2);
     
       DepUserModel du3 = new DepUserModel();
       du3.setDepUserId("du3");
       du3.setDepId("d2");
       du3.setUserId("u3");    
       depUserCol.add(du3);
     
       DepUserModel du4 = new DepUserModel();
       du4.setDepUserId("du4");
       du4.setDepId("d2");
       du4.setUserId("u4");    
       depUserCol.add(du4);
     
       DepUserModel du5 = new DepUserModel();
       du5.setDepUserId("du5");
       du5.setDepId("d2");
       du5.setUserId("u1");    
       depUserCol.add(du5);
    }
    /**
     * 完成因撤銷部門的操做所引發的與人員的交互,須要去除相應的關係
     * @param depId 被撤銷的部門對象的編號
     * @return 是否已經正確的處理了因撤銷部門所引發的與人員的交互
     */
    public boolean deleteDep(String depId) {
       //請注意:爲了演示簡單,部門撤銷後,
       //原部門的人員怎麼處理等後續業務處理,這裏就無論了
     
       //1:到記錄部門和人員關係的集合裏面,尋找跟這個部門相關的人員
       //設置一個臨時的集合,記錄須要清除的關係對象
       Collection<DepUserModel> tempCol = new ArrayList<DepUserModel>();
       for(DepUserModel du : depUserCol){
           if(du.getDepId().equals(depId)){
              //2:須要把這個相關的記錄去掉,先記錄下來
              tempCol.add(du);
           }
       }
       //3:從關係集合裏面清除掉這些關係
       depUserCol.removeAll(tempCol);
     
       return true;
    }
    /**
     * 完成因人員離職引發的與部門的交互
     * @param userId 離職的人員的編號
     * @return 是否正確處理了因人員離職引發的與部門的交互
     */
    public boolean deleteUser(String userId) {
       //1:到記錄部門和人員關係的集合裏面,尋找跟這我的員相關的部門
       //設置一個臨時的集合,記錄須要清除的關係對象
       Collection<DepUserModel> tempCol = new ArrayList<DepUserModel>();
       for(DepUserModel du : depUserCol){
           if(du.getUserId().equals(userId)){
              //2:須要把這個相關的記錄去掉,先記錄下來
              tempCol.add(du);
           }
       }
       //3:從關係集合裏面清除掉這些關係
       depUserCol.removeAll(tempCol);
  
       return true;
    }
    /**
     * 測試用,在內部打印顯示一下一個部門下的全部人員
     * @param dep 部門對象
     */
    public void showDepUsers(Dep dep) {
       for(DepUserModel du : depUserCol){
           if(du.getDepId().equals(dep.getDepId())){
              System.out.println("部門編號="+dep.getDepId()+"下面擁有人員,其編號是:"+du.getUserId());
           }
       }
    }
    /**
     * 測試用,在內部打印顯示一下一我的員所屬的部門
     * @param user 人員對象
     */
    public void showUserDeps(User user) {
       for(DepUserModel du : depUserCol){
           if(du.getUserId().equals(user.getUserId())){
              System.out.println("人員編號="+user.getUserId()+"屬於部門編號是:"+du.getDepId());
           }
       }
    }
    /**
     * 完成因人員調換部門引發的與部門的交互
     * @param userId 被調換的人員的編號
     * @param oldDepId 調換前的部門的編號
     * @param newDepId 調換後的部門的編號
     * @return 是否正確處理了因人員調換部門引發的與部門的交互
     */
    public boolean changeDep(String userId,String oldDepId, String newDepId) {
       //本示例不去實現了
       return false;
    }
    /**
     * 完成因部門合併操做所引發的與人員的交互
     * @param colDepIds 須要合併的部門的編號集合
     * @param newDep 合併後新的部門對象
     * @return 是否正確處理了因部門合併操做所引發的與人員的交互
     */
    public boolean joinDep(Collection<String> colDepIds, Dep newDep){
       //本示例不去實現了     
       return false;
    }
}

(5)測試一下,看看好用不,客戶端示例代碼以下:

public class Client {
    public static void main(String[] args) {
       DepUserMediatorImpl mediator = DepUserMediatorImpl.getInstance();
       //準備要撤銷的部門,僅僅須要一個部門編號
       Dep dep = new Dep();
       dep.setDepId("d1");
       Dep dep2 = new Dep();
       dep2.setDepId("d2");
       //準備用於測試的人員,也只須要一我的員編號
       User user = new User();
       user.setUserId("u1");
     
       //測試撤銷部門,在運行以前,輸出一下,看這我的員屬於哪些部門      
       System.out.println("撤銷部門前------------------");
       mediator.showUserDeps(user);      
       //真正執行業務,撤銷這個部門
       dep.deleteDep();    
       //再次輸出一下,看這我的員屬於哪些部門
       System.out.println("撤銷部門後------------------");
       mediator.showUserDeps(user);
     
       //測試人員離職,在運行以前,輸出一下,看這個部門下都有哪些人員
       System.out.println("---------------------------------");
       System.out.println("人員離職前------------------");
       mediator.showDepUsers(dep2);      
       //真正執行業務,人員離職
       user.dimission();   
       //再次輸出一下,看這個部門下都有哪些人員
       System.out.println("人員離職後------------------");
       mediator.showDepUsers(dep2);
    }
}

運行結果以下:

撤銷部門前------------------
人員編號=u1屬於部門編號是:d1
人員編號=u1屬於部門編號是:d2
撤銷部門後------------------
人員編號=u1屬於部門編號是:d2
---------------------------------
人員離職前------------------
部門編號=d2下面擁有人員,其編號是:u3
部門編號=d2下面擁有人員,其編號是:u4
部門編號=d2下面擁有人員,其編號是:u1
人員離職後------------------
部門編號=d2下面擁有人員,其編號是:u3
部門編號=d2下面擁有人員,其編號是:u4

好好體會一下,看看這樣作是否是變得更容易了些,並且也實現了中介者想要實現的功能,那就是讓同事對象相互分離,由中介對象統一管理它們的交互。

##3.3 中介者模式的優缺點##

  1. 鬆散耦合

中介者模式經過把多個同事對象之間的交互封裝到中介者對象裏面,從而使得同事對象之間鬆散耦合,基本上能夠作到互不依賴。這樣一來,同事對象就能夠獨立的變化和複用,而再也不像之前那樣「牽一髮而動全身」了。

  1. 集中控制交互

多個同事對象的交互,被封裝在中介者對象裏面集中管理,使得這些交互行爲發生變化的時候,只須要修改中介者對象就能夠了,固然若是是已經作好的系統,那就擴展中介者對象,而各個同事類不須要作修改。

  1. 多對多變成一對多

沒有使用中介者模式的時候,同事對象之間的關係一般是多對多的,引入中介者對象事後,中介者對象和同事對象的關係一般變成了雙向的一對多,這會讓對象的關係更容易理解和實現。

  1. 過分集中化

中介者模式的一個潛在缺點是,若是同事對象的交互很是多,並且比較複雜,當這些複雜性所有集中到中介者的時候,會致使中介者對象變得十分的複雜,並且難於管理和維護

##3.4 思考中介者模式##

  1. 中介者模式的本質

中介者模式的本質:封裝交互。

中介者模式的目的,就是用來封裝多個對象的交互,這些交互的處理多在中介者對象裏面實現,所以中介對象的複雜程度,就取決於它封裝的交互有多複雜了。

只要是實現封裝對象之間的交互功能,就能夠應用上中介者模式,而沒必要過於拘泥於中介者模式自己的結構。標準的中介者模式限制不少,致使能徹底按照標準使用中介者模式的地方並非不少,並且多集中在界面實現上。只要本質不變,稍稍變形一下,簡化一下,或許能更好的使用中介者模式

  1. 什麼時候選用中介者模式

建議在以下狀況中,選用中介者模式:

若是一組對象之間的通訊方式比較複雜,致使相互依賴、結構混亂,能夠採用中介者模式,把這些對象相互的交互管理起來,各個對象都只須要和中介者交互,從而使得各個對象鬆散耦合,結構也更清晰易懂。

若是一個對象引用不少的對象,並直接跟這些對象交互,致使難以複用該對象。能夠採用中介者模式,把這個對象跟其它對象的交互封裝到中介者對象裏面,這個對象就只須要和中介者對象交互就能夠了。

##3.5 相關模式##

  1. 中介者模式和外觀模式

這兩個模式有類似的地方,也存在很大的不一樣。

外觀模式多用來封裝一個子系統內部的多個模塊,目的是向子系統外部提供簡單易用的接口,也就是說外觀模式封裝的是子系統外部和子系統內部模塊間的交互;而中介者模式是提供多個平等的同事對象之間交互關係的封裝,通常是用在內部實現上

另外,外觀模式是實現單向的交互,是從子系統外部來調用子系統內部,不會反着來,而中介者模式實現的是內部多個模塊間多向的交互

  1. 中介者模式和觀察者模式

這兩個模式能夠組合使用。

中介者模式能夠組合使用觀察者模式,來實現當同事對象發生改變的時候,通知中介對象,讓中介對象去進行與其它相關對象的交互

相關文章
相關標籤/搜索