第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)中介者模式和觀察者模式
中介者模式可以結合觀察者模式來實現當同事物件發生改變的時候,通知中介物件,讓中介物件去進行與其他相關物件的互動。