一、概述
Mediator(中介者)模式的名称已经基本能够反映该模式的意图:用一个中介对象来封装一系列的对象之间的交互。中介者使各对象不需要显式地相互引用,从而使其耦合松散,而且可以独立地改变它们之间的交互。Mediator很象十字路口的红绿灯,每个车辆只需和红绿灯交互,而不是在各车辆之间进行交互。
Mediator与Proxy的比较
Proxy模式是简化的Mediator模式?
个人认为从功能上讲,Mediator模式与Proxy模式存在一定的相似性,但Proxy主要在于控制对被访问对象的访问,这种访问往往是单向的,而Mediator模式则在于为系统内不同的对象之间的访问提供一种媒介,这种访问往往是多向的。
Mediator与Facade的比较
很多设计模式的书中都会将Mediator模式与Facade模式进行比较,个人认为二者的区别十分明显:一个是为整个子系统对外提供一个简单化的接口,子系统的内部可能十分复杂,很多类交互作用才形成了最终的简单化的统一接口;而Mediator模式则作为系统内多个对象交互的“媒介”,负责封装对象间的交互,所有对象只需要与Mediator类交互,而不是相互之间直线联系。所以,也许在实现Facade模式时在子系统的内部采用Mediator模式可能是个不错的选择。
二、结构
Mediator模式的结构如下图所示:
图1、Mediator模式类图示意
上面的类图并不能完全反映Mediator模式的全部思想,下面结合GoF的DP一书中的一个对象图对此进行深入说明:
图2、一个典型的使用Mediator模式的对象图
在上面的类图中,各角色的分工如下:
1、Mediator(中介者):中介者定义一个接口用于与各Colleague(同事)对象通信。
2、ConcreteMediator(具体中介者):具体中介者通过协调各同事对象实现协作行为,并了解和维护它的各个同事。
3、Colleague class(同事类):每一个同事类都知道它的中介者对象,每一个同事对象在需与其他的同事通信的时候,与它的中介者通信。
三、应用
各个对象之间的交互操作非常多,每个对象的行为操作都依赖彼此对方,修改一个对象的行为,同时会涉及到修改很多其他对象的行为,如果使用Mediator模式,可以使各个对象间的耦合松散,只需关心和 Mediator的关系,使多对多的关系变成了一对多的关系,可以降低系统的复杂性,提高可修改扩展性。
在下列情况下使用中介者模式:
1、一组对象以定义良好但是复杂的方式进行通信。产生的相互依赖关系结构混乱且难以理解。
2、一个对象引用其他很多对象并且直接与这些对象通信,导致难以复用该对象。
3、想定制一个分布在多个类中的行为,而又不想生成太多的子类。
四、优缺点
Mediator模式有以下优点和缺点:
1、减少了子类生成Mediator将原本分布于多个对象间的行为集中在一起,改变这些行为只需生成Mediator的子类即可,这样各个Colleague类可被重用。
2、它将各Colleague解耦。Mediator有利于各Colleague间的松耦合,你可以独立的改变和复用各Colleague类和Mediator类。
3、它简化了对象协议用Mediator和各Colleague间的一对多的交互来代替多对多的交互。一对多的关系更易于理解、维护和扩展。
4、它对对象如何协作进行了抽象将中介作为一个独立的概念并将其封装在一个对象中,使你将注意力从对象各自本身的行为转移到它们之间的交互上来。这有助于弄清楚一个系统中的对象是如何交互的。
5、它使控制集中化中介者模式将交互的复杂性变为中介者的复杂性。因为中介者封装了协议,它可能变得比任一个Colleague都复杂。这可能使得中介者自身成为一个难于维护的庞然大物。
五、举例
Mediator模式是GoF的23种设计模式中较为容易理解的一个,我们在平时的应用中也会经常用到,但可能不会有意去抽象出一个完整的Mediator类,因为要设计一个可复用又可扩展的Mediator是很不容易的。如在MFC的文档/视图程序中,我们可能会在CWinApp派生类中添加一些供所有线程共享的数据成员或方法,这样,各线程即可通过与theApp进行交互即可达到影响其它线程的目的,在这里,CWinApp一定程度上扮演了Mediator的角色。
有人称MFC程序中Dialog在各控件间扮演着Mediator的角色,认为各控件实现时无需知道其它控件,只是将消息发送给父窗口Dialog来处理,而Dialog负责在消息处理函数中完成各控件之间的信息交互,使得控件间复杂的耦合关系变成简单的控件-Mediator(Dialog)关系。我并非十分赞同这种观点,因为我们往往将控件作为整个Dialog的一个组成部分来看待,而有意无意地忽略了它本身也是一个独立对象的事实,造成这种现象的一个关键原因可能在于MFC的消息映射机制屏蔽了控件将消息发送给对话框的过程,使得我们更愿意去认为消息是Dialog(处理)的消息,而不是(来自)控件的消息。
在Java应用中,以上状况依然存在,但是由于没有了直接的消息映射支持,JDialog往往更容易表现得像一个Mediator。
此外,还有一个观点认为,Java的Container通过选择一定的LayoutManager策略(Strategy,后面的笔记中将讲到),在各控件间起到了Mediator的作用,当新的Component被加到Container,如JPanel中时,必然会对其它控件的Layout(布局)造成影响,但各控件间是不知道对方的,他们只需知道自己的Parent Window,然后与之交互即可。这种观点也有欠妥当,但从Mediator作为各object之间的交互的媒介这一实质来看,也无可厚非。
简而言之,Mediator模式的主要作用在于为多个对象之间的交互提供一种媒介,以简化对象之间的耦合关系。下面用两个实例来说明Mediator模式的应用:
1、ChatRoom:
聊天室大家想必都知道,在下面的示例中,我们尝试实现一个布告栏式的ChatRoom,任何人想要发送消息给其他人,只需将消息发送给ChatRoom对象,由ChatRoom负责数据的转发,而不是直接与消息的接收者交互。后面的笔记中将从Observer模式的角度来解决ChatRoom问题。
#include <iostream>
#include <string>
#include <map>
using namespace std;
class Participant;
// "AbstractMediator"
struct IChatroom
{
// Methods
virtual void Register( Participant* participant ) = 0;
virtual void Send( string& from, string& to, string& message ) = 0;
};
// "AbstractColleague"
class Participant
{
friend class Chatroom;
// Fields
private:
IChatroom* pChatroom;
string name;
// Constructors
public:
/*Participant()
{
}*/
Participant( const char* name )
{
this->name = name;
}
virtual ~Participant()
{
}
// Methods
virtual void Send( string& to, string& message )
{
pChatroom->Send( name, to, message );
}
virtual void Receive( string& from, string& message )
{
cout << from.c_str() << " to " << name.c_str() << " : [" << message.c_str() << "]" << endl;
}
};
// More ConcreteColleague, omitted...
// "ConcreteMediator"
class Chatroom : public IChatroom
{
// Fields
private:
map<string, Participant*> mapParticipants; // nickname to Participant map
// Methods
public:
void Register( Participant* pParticipant )
{
mapParticipants[ pParticipant->name ] = pParticipant;
pParticipant->pChatroom = this;
}
void Send( string& from, string& to, string& message )
{
map<string, Participant*>::iterator ptr;
ptr = mapParticipants.find(to);
if ( ptr != mapParticipants.end() )
{
Participant* pto = (*ptr).second;
pto->Receive( from, message );
}
}
};
int main()
{
// Create chatroom
Chatroom c;
// Create 'chatters' and register them
Participant George("George");
Participant Paul("Paul");
Participant Ringo("Ringo");
c.Register( &George );
c.Register( &Paul );
c.Register( &Ringo );
// Chatting participants
George.Send( string("Paul"), string("Hi Paul!") );
Paul.Send( string("Ringo"), string("Good Morning!") );
Ringo.Send( string("George"), string("Hi Friend!") );
return 0;
}
2、多线程Producer-Consumer:
以下是一个多线程Producer-Comsumer的例子,在该示例中,由于无法在多个Producer、Cosumer之间建立直接的联系,因此,通过Mediator类来完成这种信息交互,当Producer要Produce时,只需与Mediator进行交互,以查询是否有空的Slot可供存放Product,而Comsumer要Comsume时,也只需与Mediator进行交互,以查询是否有Product可供Comsume。
以下是该示例的Java实现:
import java.util.*;
class Product {
int id;
Product(int id) {
this.id = id;
}
}
class Mediator {
private boolean stopFlag = false;
private Stack slot = new Stack();
private int slotCount;
public Mediator(int slotCount) {
this.slotCount = slotCount;
}
public boolean stop() {
return stopFlag;
}
public void stop(boolean flag) {
stopFlag = true;
}
public boolean put(Product product) {
synchronized( slot ) { // or synchronized on Mediator.class, but on slot is better and reasonable
if ( slot.size() >= slotCount ) {
return false;
}
slot.push( product );
}
return true;
}
public Product get() {
synchronized( slot ) {
if ( slot.empty() )
return null;
Product product = (Product)slot.pop();
return product;
}
}
}
class Producer extends Thread {
private Mediator med;
private int id;
private static int num = 1;
public Producer(Mediator m) {
med = m;
id = num++;
}
public void run() {
Product product;
while ( !med.stop() ) {
product = new Product((int) (Math.random() * 100));
synchronized (System.out) {
System.out.println("Producer[" + id + "] produces Product["
+ product.id + "]");
}
while ( !med.stop() && !med.put(product) ) { // if put failed, try to put again and again.
try {
sleep( 100 );
} catch (InterruptedException ie) {
}
}
try {
sleep( 100 );
} catch (InterruptedException ie) {
}
}
}
}
class Consumer extends Thread {
private Mediator med;
private int id;
private static int num = 1;
public Consumer(Mediator m) {
med = m;
id = num++;
}
public void run() {
Product product;
while ( !med.stop() ) {
product = med.get();
if ( product != null ) {
synchronized (System.out) {
System.out.println("Consumer[" + id + "] is consuming Product["
+ product.id + "]");
}
}
try {
sleep( 100 );
} catch (InterruptedException ie) {
}
}
}
}
class MediatorDemo {
public static void main(String[] args) {
Mediator med = new Mediator(2);
Thread thread[] = { new Producer(med), new Producer(med),
new Consumer(med), new Consumer(med), new Consumer(med) };
for (int i = 0; i < thread.length; i++)
thread[i].start();
// before stop all threads, sleep 1 second
try {
Thread.sleep(1000);
} catch (InterruptedException ie) {
}
med.stop(true);
// Wait for all threads to return
try {
for (int i = 0; i < thread.length; i++) {
thread[i].join();
}
} catch (InterruptedException ie) {
}
}
}
由于JDK为Java程序员提供了丰富的线程及线程同步支持,因此,要用Java解决上述问题,是比较简单的,但是C++程序员就没那么幸运了,因为C++到目前为止并没有规范线程支持(据说以后会有,但是按照C++标准n年更新一次的速度,谁知道什么时候会有,好在我们不用等了,在一些关注应用的库中已经能够提供了一些十分稳定的Thread支持)。
下面是上述示例的等价C++实现,代码中使用了Boost.Thread(关于如何在VC下使用boost::thread,见参考3;关于如何在VC6下编译boost,见参考4,VC2003/VC2005的用户请自行相应修改)。如果你不想使用boost,可以将其中的thread换成等价的其它线程库实现中的等价类,如log4cxx::helpers::Thread、ACE_Thread/ACE_Thread_Manager,或者参考3中的跨平台线程库等,当然,MFC的CWinThread对于VC++开发人员应该是最简便的选择。
虽然使用了boost::thread,但由于缺少语言本身的支持(如果C++能像Java一样提供synchronized关键字,实现会简化很多),下面的实现仍然显得有点臃肿,你在读这个例子时如果对boost::thread的使用不感兴趣,完全可以将它想象成一个普通的类,无需考虑具体的使用细节,只要明白其语义及整个代码所表现的Mediator模式的实质即可。
#include <boost/thread/thread.hpp>
#include <boost/thread/mutex.hpp>
#include <boost/thread/condition.hpp>
#include <boost/thread/xtime.hpp>
#include <iostream>
#include <time.h> // for time()
#include <Windows.h> // for Sleep, change it for other platform
typedef boost::mutex::scoped_lock scoped_lock;
boost::mutex io_mutex;
class Product
{
int num;
public:
Product(int num) : num(num) {}
friend std::ostream& operator<< (std::ostream& os, Product& product)
{
return os << product.num;
}
};
class Mediator
{
private:
boost::condition cond;
boost::mutex mutex;
Product** pSlot; // product buffer/slot
unsigned int slotCount, // buffer size
productCount; // current product count
bool stopFlag; // should all thread stop or not
public:
Mediator(const int slotCount) : slotCount(slotCount), stopFlag(false), productCount(0)
{
pSlot = new Product*[slotCount];
}
virtual ~Mediator()
{
for (int i = 0; i < static_cast<int>(productCount); i++)
{
delete pSlot[i];
}
delete [] pSlot;
}
bool Stop() const { return stopFlag; }
void Stop(bool) { stopFlag = true; }
void NotifyAll()
{
cond.notify_all();
}
bool Put( Product* pProduct)
{
scoped_lock lock(mutex);
if (productCount == slotCount)
{
{
scoped_lock lock(io_mutex);
std::cout << "Buffer is full. Waiting..." << std::endl;
}
while (!stopFlag && (productCount == slotCount))
cond.wait(lock);
}
if (stopFlag) // it may be notified by main thread to quit.
return false;
pSlot[ productCount++ ] = pProduct;
cond.notify_one(); // this call may cause *pProduct to be changed if it wakes up a consumer
return true;
}
bool Get(Product** ppProduct)
{
scoped_lock lock(mutex);
if (productCount == 0)
{
{
scoped_lock lock(io_mutex);
std::cout << "Buffer is empty. Waiting..." << std::endl;
}
while (!stopFlag && (productCount == 0))
cond.wait(lock);
}
if (stopFlag) // it may be notified by main thread to quit.
{
*ppProduct = NULL;
return false;
}
*ppProduct = pSlot[--productCount];
cond.notify_one();
return true;
}
};
class Producer
{
private:
Mediator* pMediator;
static unsigned int num;
unsigned int id; // Producer id
public:
Producer(Mediator* pMediator) : pMediator(pMediator) { id = num++; }
void operator() ()
{
Product* pProduct;
srand( (unsigned)time( NULL ) + id ); // each thread need to srand differently
while (!pMediator->Stop())
{
pProduct = new Product( rand() % 100 );
// must print product info before call Put, as Put may wake up a consumer
// and cause *pProuct to be changed
{
scoped_lock lock(io_mutex);
std::cout << "Producer[" << id << "] produces Product["
<< *pProduct << "]" << std::endl;
}
if (!pMediator->Put(pProduct)) // this function only fails when it is notified by main thread to exit
delete pProduct;
Sleep(100);
}
}
};
unsigned int Producer::num = 1;
class Consumer
{
private:
Mediator* pMediator;
static unsigned int num;
unsigned int id; // Consumer id
public:
Consumer(Mediator* pMediator) : pMediator(pMediator) { id = num++; }
void operator() ()
{
Product* pProduct = NULL;
while (!pMediator->Stop())
{
if (pMediator->Get(&pProduct))
{
scoped_lock lock(io_mutex);
std::cout << "Consumer[" << id << "] is consuming Product["
<< *pProduct << "]" << std::endl;
delete pProduct;
}
Sleep(100);
}
}
};
unsigned int Consumer::num = 1;
int main()
{
Mediator mediator(2); // we have only 2 slot to put products
// we have 2 producers
Producer producer1(&mediator);
boost::thread thrd1(producer1);
Producer producer2(&mediator);
boost::thread thrd2(producer2);
// and we have 3 consumers
Consumer consumer1(&mediator);
boost::thread thrd3(consumer1);
Consumer consumer2(&mediator);
boost::thread thrd4(consumer2);
Consumer consumer3(&mediator);
boost::thread thrd5(consumer3);
// wait 1 second
Sleep(1000);
// and then try to stop all threads
mediator.Stop(true);
mediator.NotifyAll();
// wait for all threads to exit
thrd1.join();
thrd2.join();
thrd3.join();
thrd4.join();
thrd5.join();
return 0;
}
参考:
1、http://www.dofactory.com/Patterns/PatternMediator.aspx
2、http://home.earthlink.net/~huston2/dp/MediatorDemosJava
3、http://blog.vckbase.com/billdavid/archive/2005/03/07/3412.html