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)中介者模式和观察者模式
中介者模式可以结合观察者模式来实现当同事对象发生改变的时候,通知中介对象,让中介对象去进行与其他相关对象的交互。