1. 程式人生 > >第19章 行為型模式—中介者模式

第19章 行為型模式—中介者模式

1. 中介者模式(Mediator Pattern)的定義

(1)定義:用一個中介物件來封裝一系統物件互動。中介者使得各物件不需要顯式地相互引用,從而使其耦合鬆散,而且可以獨立地改變它們之間的互動。

     

  ①中介者模式主要用來將同事類之間網狀結構變為星狀結構,使同事類之間的關係變的清晰一些。

  ②所有物件只跟中介者物件進行通訊,相互之間不再有聯絡,這樣也能夠集中控制這些物件的互動關係

(2)中介者模式的結構和說明

  ①Mediator: 中介者介面。在裡面定義各個同事之間互動需要的方法,可以是公共的通訊方法,比如changed方法,大家都可以用(一般會傳入同事類的this指標),也可以是小範圍的互動方法。

  ②ConcreteMediator:具體中介者實現物件。這需要瞭解並維擴各個同事物件,並負具體的協調各同事物件的互動關係。

  ③Colleague:同事類的定義,通常實現為抽象類,主要負責約束同事物件的型別,並能實現一些具體同事類之間的公共功能。同時,一般會持有中介者的引用

  ④ConcreteColleague:具體的同事類,實現自己的業務,在需要與其他同事通訊的時候,就與持有的中介者通訊,中介者會負責與其他的同事互動。

【程式設計實驗】班級QQ群

//示意圖

    

//UML圖

//行為模式——中介者模式
//場景:班級通訊

#include <iostream>
#include <string>
#include <vector>

using namespace std;

class Colleage; //前向宣告

//*******************************************抽象中介者**************************************
//抽象中介者
class Mediator
{
public:
    virtual void addStudent(Colleage* student) = 0;
    //通知
    virtual void notify(Colleage* student) = 0;
    //兩個同學私下交流
    virtual void chat(Colleage* student1,Colleage* student2) = 0;
};

//*******************************************抽象同事類**************************************
//抽象同事類
class Colleage
{
private:
    string name;
    string content;
protected:
    Mediator* mediator;
public:
    Colleage(Mediator* mediator, string name="")
    {
        this->mediator = mediator;
        this->name = name;

        this->mediator->addStudent(this);
    }

    string& getName()
    {
        return this->name;
    }

    void setContent(string content)
    {
        this->content = content;
    }

    string& getContent()
    {
        return content;
    }

    virtual void talk() = 0;

    void inform()
    {
        talk();
        mediator->notify(this);
    }
};

//具體的同事類:班長
class Monitor : public Colleage
{
public:
    Monitor(Mediator* mediator, string name=""):Colleage(mediator,name){}

    virtual void talk()
    {
        cout <<"班長 說:" << getContent() << endl;
    }
};

//具體的同事類:團支書
class YouthLeague : public Colleage
{
public:
    YouthLeague(Mediator* mediator, string name=""):Colleage(mediator,name){}

    virtual void talk()
    {
        cout <<"團支書 說:" << getContent() << endl;
    }
};

//具體的同事類:同學A
class StudentA : public Colleage
{
public:
    StudentA(Mediator* mediator, string name=""):Colleage(mediator,name){}

    virtual void talk()
    {
        cout <<"學生A 說:" << getContent() << endl;
    }
};

//具體的同事類:同學A
class StudentB : public Colleage
{
public:
    StudentB(Mediator* mediator, string name=""):Colleage(mediator,name){}

    virtual void talk()
    {
        cout <<"學生B 說:" << getContent() << endl;
    }
};

//具體的中介者(如QQ通訊平臺)
class QQMediator: public Mediator
{
private:
    vector<Colleage*> studentList;
public:
    void addStudent(Colleage* student)
    {
        studentList.push_back(student);
    }

    void notify(Colleage* student)
    {
        //student發出通知,其他同學回覆
        vector<Colleage*>::iterator iter = studentList.begin();
        while(iter != studentList.end())
        {
            //其他同學的回覆
            if(*iter != student)
            {
                (*iter)->talk();
            }
            ++iter;
        }
    }

    //私下交流
    void chat(Colleage* student1,Colleage* student2)
    {
        //學生1說話
        student1->talk();
        //學生2回答
        student2->talk();
    }
};

int main()
{
    //***********************初始化QQ聊天環境********************
    QQMediator qq;
    Monitor mon(&qq,"Minitor");
    YouthLeague youth(&qq,"YouthLeague");
    StudentA stuA(&qq,"StudentA");
    StudentB stuB(&qq, "StudentB");


    //***************班級發通知********************
    mon.setContent("明天下午2點開年段會,收到請回復^^。");
    youth.setContent("知道了,肯定到!!");
    stuA.setContent("收到了,一定準時到!!");
    stuB.setContent("收到了,但明天要去面試,特請假一下!!");

    //開始發通知
    mon.inform();

    //*******************兩個同學私下交流**************
    cout << endl << "下面是兩個同學的私下交流:" << endl;
    mon.setContent("你覺得咱們地理老師課講得怎麼樣?");
    stuA.setContent("我覺得講的不夠生動,還點名,不太好!!!");
    qq.chat(&mon,&stuA);

    return 0;
}

2. 思考中介者模式

(1)中介者模式的本質封裝互動。中介者的目的,就是用來封裝多個物件的互動,這些互動的處理多在中介者物件裡面實現。只要是實現封裝物件之間的互動功能,就可用中介者模式,而不必過於拘泥於中介者模式本身的結構。

(2)需要Mediator介面嗎

  這取決於是否會提供多個不同的中介者實現。如果中介者實現只有一個的話,而且預計中也沒有擴充套件的需求,那就可以不定義Mediator介面。如果中介者實現不只一個,或者預計有擴充套件的要求,那麼就需要定義Mediator介面。讓各個同事類來面向中介者介面程式設計。

(3)同事關係

  在標準的中介者模式中,將使用中介者物件來互動的那些物件稱為同事類,在中介者模式中要求這些類都要繼承相同的類,也就是說,這些物件從某個角度來講是同一個型別

,算是兄弟物件。

(4)同事和中介者的關係

  ①當一個同事物件發生了改變,需要主動通知中介者,讓中介者去處理與其他同事物件相關的互動。

  ②同事物件需要知道中介者物件是誰,反過來,中介者物件也需要知道相關的同事物件,這樣才能與同事物件進行互動。

  ③中介者物件與同事物件之間是相互依賴的。

(5)如何實現同事和中介者的通訊

  ①同事類持有中介者物件,可以通過Mediator介面中定義一個特殊的通知介面(如changed),並把this當做引數傳入,這樣在中介者物件裡面,就可以去獲取這個同事物件的例項或當中的資料了。中介者物件裡面記錄著各個同事,會根據從changed介面中傳入來的物件,判斷下一步的動作。

  ②另一種實現方式可以採用觀察者模式,把Mediator實現成為觀察者,而各個同事類實現成為Subject,這樣同事類發生了改變,會通知Mediator。Mediator在接到通知的以後,會與相應的同事物件進行互動。

【程式設計實驗】用電腦看電影

//行為模式——中介者模式
//場景:使用電腦來看電影
//中介者:主機板
//同事類:CPU、記憶體、光碟機、顯示卡、音效卡等

#include <iostream>
#include <string>
#include <vector>

using namespace std;

class Colleage; //前向宣告

//*******************************************抽象中介者**************************************
//抽象中介者
class Mediator
{
public:
    //同事物件在自身改變的時候呼叫這個介面來通知中介者
    virtual void changed(Colleage* colleague) = 0;
};

//*******************************************抽象同事類**************************************
//抽象同事類
class Colleage
{
protected:
    Mediator* mediator;
public:
    Colleage(Mediator* mediator)
    {
        this->mediator = mediator;
    }
    //獲取當前同事類對應的中介者物件
    Mediator& getMediator()
    {
        return *mediator;
    }
};

//具體的同事類:光碟機
class CDDriver : public Colleage
{
private:
    string data;
public:
    CDDriver(Mediator* mediator):Colleage(mediator){}

    //讀取光碟
    void readCD()
    {
        //逗號前是視訊顯示的資料,逗號後是聲音
        data = "設計模式,值得好好研究";
        //通知中介者(主機板),自己的狀態發生了改變
        mediator->changed(this);
    }

    string& getData()
    {
        return data;
    }
};

//具體的同事類:CPU
class CPU : public Colleage
{
private:
    string videoData; //分解出來的視訊資料
    string soundData; //分解出來的聲音資料
public:
    CPU(Mediator* mediator):Colleage(mediator){}

    string& getVideoData()
    {
        return videoData;
    }

    string& getSoundData()
    {
        return soundData;
    }

    //處理資料,把資料分成音訊和視訊資料
    void executeData(string data)
    {
        int nPos = data.find(",");
        videoData = data.substr(0,nPos);
        soundData = data.substr(nPos+1,data.length()-nPos);
        //通知主機板,CPU的工作完成
        mediator->changed(this);
    }
};

//具體的同事類:顯示卡類
class VideoCard : public Colleage
{
private:
    string data; //被顯示的資料
public:
    VideoCard(Mediator* mediator):Colleage(mediator){}

    //顯示資料
    void showData(string data)
    {
        cout << "您正在觀看的是:" << data << endl;
    }
};

//具體的同事類:音效卡類
class SoundCard : public Colleage
{
private:
    string data; //被播放的聲音資料
public:
    SoundCard(Mediator* mediator):Colleage(mediator){}

    //顯示資料
    void soundData(string data)
    {
        cout << "畫外音:" << data << endl;
    }
};

//*************************************具體中介者****************
//主機板類
class MainBoard : public Mediator
{
private:
    CDDriver* cdDriver;
    CPU* cpu;
    VideoCard* videoCard;
    SoundCard* soundCard;

    //處理光碟機讀取資料以後與其他物件的互動
    void opeCDDriverReadData(CDDriver* cd)
    {
        //1.獲取光碟機讀取的資料
        string data = cd->getData();
        //2.把這些資料傳給CPU進行處理
        cpu->executeData(data);
    }

    //CPU處理完資料後與其他物件的互動
    void opeCPU(CPU* cpu)
    {
        //1.先取出CPU處理後的資料
        string& videoData = cpu->getVideoData();
        string& soundData = cpu->getSoundData();

        //2. 把資料傳遞給顯示卡和音效卡展示出來
        videoCard->showData(videoData);
        soundCard->soundData(soundData);
    }

public:
    void setCDDrriver(CDDriver* cdDriver)
    {
        this->cdDriver = cdDriver;
    }

    void setCPU(CPU* cpu)
    {
        this->cpu = cpu;
    }

    void setVideoCard(VideoCard* videoCard)
    {
        this->videoCard = videoCard;
    }

    void setSoundCard(SoundCard* soundCard)
    {
        this->soundCard = soundCard;
    }

    //接收通知
    void changed(Colleage* colleage)
    {
        //從光碟機來的通知
        if(colleage == cdDriver)
        {
            opeCDDriverReadData((CDDriver*)colleage);
        }
        else if(colleage == cpu)
        {
            //表示CPU處理完了
            opeCPU((CPU*)colleage);
        }
    }
};

int main()
{
    //1.建立中介者——主機板物件
    MainBoard mediator;
    //2.建立同事類
    CDDriver cd(&mediator);
    CPU cpu(&mediator);
    VideoCard vc(&mediator);
    SoundCard sc(&mediator);

    //3.讓中介者知道所有的同事
    mediator.setCDDrriver(&cd);
    mediator.setCPU(&cpu);
    mediator.setVideoCard(&vc);
    mediator.setSoundCard(&sc);

    //4.開始看電影,把光碟放入光碟機,光碟機開始讀盤
    cd.readCD();

    return 0;
}

3. 廣義的中介者

(1)標準中介者模式的問題

  ①同事物件都要從一個公共的父類繼承。在這實際開發中,很多相互互動的物件本身是沒有公共父類的,強行加上一個父類,會讓這些物件實現起來特別彆扭。

  ②同事類必須持有中介者物件嗎?在標準的中介者模式中,中介者物件作為屬性並通過構造方法注入到同事類中的。而實際開發中,可以把中介者物件做成單例,直接在同事類的方法裡面去呼叫中介者物件,而無須將中介者作為同事類的成員變數。

  ③在實際開發中,很常見的情況是不需要中介者介面的,而且中介者物件也不需要建立多個例項。因為中介者是用來封裝和處理同事物件的關係的,它一般被實現為單例。

  ④中介者物件是否需要持有所有的同事?在標準的中介者模式中會將所有的同事類作為成員變數(或儲存在連結串列中)這是一種很強的依賴關係。在實現中,可以在中介者處理的方法裡面去建立或獲取,或者從引數傳入需要的同事物件

  ⑤中介者物件只是提供一個公共的方法來接受同事物件的通知嗎?在標準的中介者中只提供一個公共方法,這樣還是要去區分到底是哪個同事類發過來的通知。在實際的開發中,通常會提供具體的業務通知方法,這樣就不用再去判斷到底是什麼物件,其具體的什麼業務了。

(2)廣義的中介者模式(也是簡化的中介者模式)

  ①通常去掉同事物件的父類,這樣可以讓任意物件,只要需要相互互動,就可以成為同事。

  ②通常不定義Mediator介面,把具體的中介者物件實現成單例。

  ③同事物件不再持有中介者,需是在需要的時候直接獲取中介者物件並呼叫;中介者也不再持有同事物件,而是在具體的處理方法裡面去建立或者獲取,或者從引數傳入需要的同事物件。

【程式設計實驗】人事管理

//引入中介者的人事管理示意圖

//UML示意圖

//行為模式——中介者模式
//場景:人事管理系統
//一個部門可能有多個人,同時一個人也可以加入多個部門,即
//部門和人員是多對多的關係。因此如果人員和部門直接打交道
//那樣人員或部門的內部勢必要引用多個部門或人員的引用,這樣
//耦合性太強,可以把部門和人員的關係放入中介者中,通過中介者
//來維護部門和人員的關係,這樣可以將部門和人員解耦。同時集中
//管理人員和部門之間的關係((如刪除某個人或部門時的邏輯)

#include <iostream>
#include <string>
#include <vector>

using namespace std;

//**********************輔助類****************
//描述部門和人員關係的類(相當於資料表的一行記錄)
class DepUserModel
{
private:
    //用於部門和人員關係的編號
    string depUserId; //用做主鍵
    string depId;      //部門編號
    string userId;     //人員編號
public:
    string& getDepUserId()
    {
        return depUserId;
    }
    void setDepUserId(string depUserId)
    {
        this->depUserId = depUserId;
    }

    string& getDepId()
    {
        return depId;
    }
    void setDepId(string depId)
    {
        this->depId = depId;
    }

    string& getUserId()
    {
        return userId;
    }

    void setUserId(string userId)
    {
        this->userId = userId;
    }
};

//**********************中介者****************************
//實現部門和人員互動的中介者實現類
class DepUserMediatorImpl
{
private:
    static DepUserMediatorImpl* mediator;

    //建構函式私有化
    DepUserMediatorImpl()
    {
        initTestData(); //初始化測試資料
    }
private:
    vector<DepUserModel*> depUser;

    void initTestData()
    {
        //準備一些測試資料
        DepUserModel* dum = new DepUserModel();
        dum->setDepUserId("du1");
        dum->setDepId("d1");
        dum->setUserId("u1");
        depUser.push_back(dum);

        dum = new DepUserModel();
        dum->setDepUserId("du2");
        dum->setDepId("d1");
        dum->setUserId("u2");
        depUser.push_back(dum);

        dum = new DepUserModel();
        dum->setDepUserId("du3");
        dum->setDepId("d2");
        dum->setUserId("u3");
        depUser.push_back(dum);

        dum = new DepUserModel();
        dum->setDepUserId("du4");
        dum->setDepId("d2");
        dum->setUserId("u4");
        depUser.push_back(dum);

        dum = new DepUserModel();
        dum->setDepUserId("du5");
        dum->setDepId("d2");
        dum->setUserId("u1");
        depUser.push_back(dum);
    }
public:
    //完成因撤銷部門的操作所引起的與人員的互動,需要去除相應的關係
    bool deleteDep(string depId)
    {
        //為了演示簡單,部門撤銷後,原部門的人員怎麼處理這裡不管了
        //到記錄部門和人員關係的集合裡面,尋找跟這個部門相關的人員
        vector<DepUserModel*>::iterator iter = depUser.begin();
        while(iter != depUser.end())
        {
            if( (*iter)->getDepId() == depId)
            {
                delete (*iter);
                iter = depUser.erase(iter);
            }
            else
                ++iter;
        }
        return true;
    }

    //完成因人員離職引起的與部門互動
    bool deleteUser(string userId)
    {
        //到記錄部門和人員關係的集合裡面,尋找跟這個人員相關
        //的部門,並刪除那些記錄。
        vector<DepUserModel*>::iterator iter=depUser.begin();
        while(iter != depUser.end())
        {
            if((*iter)->getUserId() == userId)
            {
                delete (*iter);
                iter = depUser.erase(iter);
            }
            else
                ++iter;
        }
        return true;
    }

    //完成因人員調換部門引起的與部門互動
    bool changeDep(string userId,string oldDepId,string newDepId)
    {
        //本例不去實現了
        return false;
    }

    //完成因部門合併操作所引起的與人員的互動
    //@param depIds 需要合併的部門編號的集合
    //@param newDep 合併後新的部門物件
    bool joinDep(vector<string> depIds, string** newDepId)
    {
        //本例不去實現了
        return false;
    }

    //測試用,在內部列印顯示一個部門下的所有人員
    void showDepUsers(string depId)
    {
        vector<DepUserModel*>::iterator iter=depUser.begin();
        while(iter != depUser.end())
        {
             if((*iter)->getDepId() == depId)
             {
                  cout <<"部門編號=" << depId
                       <<"下面擁有人員,其編號是:"
                       <<(*iter)->getUserId() << endl;
             }
             ++iter;
        }
    }

    //測試用,在內部列印顯示一個人員所屬的部門
    void showUserDeps(string userId)
    {
        vector<DepUserModel*>::iterator iter=depUser.begin();
        while(iter != depUser.end())
        {
             if((*iter)->getUserId() == userId)
             {
                  cout <<"人員編號=" << userId
                       <<"所屬的部門編號是:"
                       <<(*iter)->getDepId() << endl;
             }
             ++iter;
        }
    }

public:
    static DepUserMediatorImpl* getInstance()
    {
        if(mediator == NULL)
            mediator = new DepUserMediatorImpl();

        return mediator;
    }
};

DepUserMediatorImpl* DepUserMediatorImpl::mediator = NULL;

//部門類
class Dep
{
private:
    string depId;   //部門編號
    string depName; //部門名稱
public:
    void setDepId(string depId)
    {
        this->depId = depId;
    }

    string& getDepId()
    {
        return depId;
    }

    void setDepName(string depName)
    {
        this->depName = depName;
    }

    string& getDepName()
    {
        return depName;
    }

    //撤銷部門
    bool deleteDep()
    {
        //1.部門類只與中介打交通,先通過中介者去除掉所有與這個部門相關的部門和人員的關係
        DepUserMediatorImpl* mediator = DepUserMediatorImpl::getInstance();
        mediator->deleteDep(depId);

        //2.真正刪除掉這個部門

        return true;
    }
};

//人員類
class User
{
private:
    string userId;
    string userName;
public:
    string& getUserId()
    {
        return userId;
    }

    void setUserId(string userId)
    {
        this->userId = userId;
    }

    string& getUserName()
    {
        return userName;
    }

    void setUserName(string userName)
    {
        this->userName = userName;
    }

    //人員離職
    bool dimission()
    {
        //1.人員類只與中介打交通,先通過中介者去除掉所有與這個人員相關的部門和人員的關係
        DepUserMediatorImpl* mediator = DepUserMediatorImpl::getInstance();
        mediator->deleteUser(userId); //離職

        //2.然後刪除這個人員(注意,實際開發中,是不會真正刪除人員記錄的。

        return true;
    }

};

int main()
{
    DepUserMediatorImpl& mediator = *(DepUserMediatorImpl::getInstance());

    //準備要撤銷的部門
    Dep dep;
    dep.setDepId("d1");
    Dep dep2;
    dep2.setDepId("d2");

    //準備用於測試的人員
    User user;
    user.setUserId("u1");


    //測試撤銷部門,在執行之前,輸出一下,看這個人員屬於哪些部門
    cout <<"撤銷部門前-----------------------------" << endl;
    mediator.showUserDeps(user.getUserId());

    //真正執行業務撤銷這個部門
    dep.deleteDep();

    //再次輸出一下,看這個人員屬於哪些部門
    cout <<"撤銷部門後-----------------------------" << endl;
    mediator.showUserDeps(user.getUserId());

    //測試人員離職,在執行之前,輸出一下,看這個部門下都有哪些人員
    cout <<"人員離職前-----------------------------" << endl;
    mediator.showDepUsers(dep2.getDepId());

    //真正執行業務,人員離職
    user.dimission();

    //再次輸出一下,看這個人員屬於哪些部門
    cout <<"人員離職後-----------------------------" << endl;
    mediator.showDepUsers(dep2.getDepId());

    return 0;
}
/*輸出結果:
撤銷部門前-----------------------------
人員編號=u1所屬的部門編號是:d1
人員編號=u1所屬的部門編號是:d2
撤銷部門後-----------------------------
人員編號=u1所屬的部門編號是:d2
人員離職前-----------------------------
部門編號=d2下面擁有人員,其編號是:u3
部門編號=d2下面擁有人員,其編號是:u4
部門編號=d2下面擁有人員,其編號是:u1
人員離職後-----------------------------
部門編號=d2下面擁有人員,其編號是:u3
部門編號=d2下面擁有人員,其編號是:u4
*/

4. 中介者模式的優缺點

(1)優點

  ①鬆散耦合:中介者把多個同事物件之間的互動封裝到中介者物件裡面,從而使得同事物件之間鬆散耦合,基本上可以做到互不依賴。這樣同事物件可以獨立變化和複用。

  ②集中控制:多個同事物件的互動被封裝在中介者物件裡面集中管理地,使得這些互動行為發生變化的時候,只需要修改中介者物件就可以了。

  ③多對多變一對多的關係

(2)缺點

  ①潛在的過度集中化。如果同事物件的互動非常多,而且比較複雜。當這些複雜性全部集中到中介者的時候,會導致中介者物件變得十分複雜,難於管理和維護。

  ②由於“中介“承擔了較多的責任,所以一旦這個中介物件出現了問題,那麼整個系統就會受到重大的影響。

5. 中介者的應用場景

(1)同事類之間是網狀結構的關係,可以考慮使用中介者模式。它會將網狀結構變為星狀結構,使同事類之間的關係變的清晰一些。

(2)一個物件引用很多物件,並直接跟這些物件互動,導致難以複用該物件,可以採用中介者模式,把這個物件跟其他物件的互動封裝到中介者物件裡面。

6. 相關模式

(1)中介者模式與外觀模式

  ①外觀模式多用來封裝一個子系統內部的多個模式,目的是向子系統外部提供簡單易用的介面。也就是說外觀模式封裝的是子系統外部和子系統內部模組間的互動。而中介者模式是提供多個平等的同事物件之間互動關係的封裝,一般是用在內部實現上。

  ②外觀模式的實現是單向的互動,是從子系統外部來呼叫子系統內部,不會反著過來;而中介者模式實現是內部多個模組間多向的互動

(2)中介者模式和觀察者模式

  中介者模式可以結合觀察者模式來實現當同事物件發生改變的時候,通知中介物件,讓中介物件去進行與其他相關物件的互動。