设计模式-中介者Mediator深思(对象/模块/进程通信)

概述

记得初识设计模式时,有本书称她们都是选美比赛的漂亮妞,Here,借用下其中的语调,近来,被“其貌不扬”的中介者小姐的魅力深深吸引,我们来谈点别的。早些时候读GOF设计模式,就那么泛泛的看,也没啥感觉,但是后来经过了些项目的历练,有了些自己的思考,再回重读,有点小飘,感觉像打通了任督二脉,看其中的字字句句都是浓缩的精华…

场景

想写这篇文章,始于如下条件和场景。第一,前不久,利用中介者模式,实现了一个数据通信模块COMM,该模块使用中介者模式完成了设备集合与协议集合间的数据中转,中介者角色充当组件间的通信的中转中心,使得原本会发展为复杂网络的通信关系变成了清晰的星形关系。
第二,在系统设计中,COMM主要负责通信报文的组包、应用数据拆包等,其上一层叫应用数据服务DSERVER,它主要负责应用数据的派分和发送序列控制。她们之间之间主要的数据交互的理想接口应该是这样的:

COMM接口DSERVER接口
CommInputAppData(data);
CommOutPutAppData(data);
DserverInputAppData(data);
DServerOutPutAppData(data);
接口很简洁漂亮,但是问题来了,怎么将接口串联起来(或者说如何实现两个模块的运行时控制流)-?

方案1:
最笨的方法,包含法,将COMM模块关联到DSERVER模块,如成员变量。具体做法如下:

  1. 将DserverInputAppData养成一个回调函数通过COMM的其他接口注册到其中,然后在CommOutPutAppData函数中触发回调过程。
  2. 在DServerOutPutAppData函数中直接通过COMM对象调用她的CommInputAppData;
class DSERVER
{
public:
	//be dropped
private:
	COMM *m_pCOMM;	//dependency
}
static bool DSERVER::DServerInPutAppData(DATA data) 
{
	//deal app data from COMM //FUNC Pointer
}
bool DSERVER::InitCOMM2DSERVER
{
	m_pCOMM->RegistDServerProcess2Comm(DServerInPutAppData)
}
bool DSERVER::DServerOutPutAppData(DATA data)
{
	m_pCOMM->CommInputAppData(data);
}

class COMM
{
private:
	FuncPointerComm2Deal m_funcComm2Deal;
}
bool COMM::CommOutPutAppData(data)
{
	m_funcComm2Deal(data);  //CALLBACK FUNC
}

这种方式的缺点并不难理解,COMM和DSERVER为分别的动态库,这样的强耦合,在代码中体现是,DSERVER包含了COMM的头文件,是知道它的,这不符合设计原则-迪米特法则,更不符合高内聚低耦合的口号。

方案2:
借用Qt框架的信号槽机制,在模块间构建通信契约,然后在它们公共的客户端中,进行必要的连接初始化。信号槽机制,尤其是其直接连接方式,其本质上就是一种回调函数及其使用的封装,但是这种封装带来的是一种意义非凡的方便性。
在Qt框架下,借助信号槽,可以将方案1种本来需要强关联的两个模块解耦,使得它们仅通过公共的客户端就能建立其一种运行流程,其真实效果与使用回调函数一致。

class COMM
{
signals:
	void SignalCommOutputAppData(DATA data, bool *bOk);
public:
	//外部客户端调用
	bool CommInputAppData(DATA data);
	//内部流程调用
	bool CommOutputAppData(DATA data);
}
bool COMM::CommOutputAppData(DATA data)
{
	bool bOk = false;
	emit SignalCommOutputAppData(data, &bOk);
	return bOk;  //true or false 
}

class DServer
{
signals:
	void SignalDServerOutputAppData(DATA data, bool *bOk);
public:
	//外部客户端调用
	bool DServerInputAppData(DATA data);
	//内部流程调用
	bool DServerOutputAppData(DATA data);
}
bool DServer::DServerOutputAppData(DATA data)
{
	bool bOk = false;
	emit SignalDServerOutputAppData(data, &bOk);
	return bOk;  //true or false 
}

//客户端的实现
class COMM2DSERVERclient
{
public:
	bool InitCommAndDServerConnect();
private slots:
	void SlotCommDriveDServer(DATA data, bool *bOk);
	void SlotDServerDriveComm(DATA data, bool *bOk);
private:
	COMM    *m_pCommObj;
	DServer *m_DServerObj;
}
void COMM2DSERVERclient::SlotDServerDriveComm(DATA data, bool *bOk)
{
	*bOk = m_pCommObj->CommInputAppData(data);
}
void COMM2DSERVERclient::SlotCommDriveDServer(DATA data, bool *bOk)
{
	*bOk = m_DServerObj->DServerInputAppData(data);
}

方案3:
理想很美好,现实很骨感。若在模块间使用中介者模式,代码格式尤其是接口模式将会变得很漂亮,就可以完全是是上述表格中期待的样子。具体的代码大约是这样的:

//具体中介者实现
ConcreteMediator::SendDataCommToDServer(DATA data)
{
	//Here data came from COMM Module
	pDeserv->DServerInPutAppData(data);
}
ConcreteMediator::SendDataDServerToComm()
{
	//Here data came from DSERVER Module
	pDeserv->CommInputAppData(data);
}

//具体数据通信模块接口实现
COMM::CommOutputAppData(DATA data)
{
	//other deal..
	m_pAbstractMediator->SendDataCommToDServer(data);
}
COMM::CommInputAppData(DATA data)
{
	//other deal.. nothing with mediator interface
}

//具体数据服务模块接口实现
DSERVER::DServerOutPutAppData(DATA data)
{
	//other deal..
	m_pAbstractMediator->SendDataDServerToComm(data);
}
DSERVER::DServerInPutAppData(DATA data)
{
	//other deal.. nothing with mediator interface
}

观摩完丰满的理想,接下来就是看现实的骨感了:

//class ConcreteMediator : public AbstractMediator

class COMM /*: public MODULE*/
{
	COMM(AbstractMediator *pMediator)
}

class DSERVER /*: public MODULE*/
{
	DSERVER(AbstractMediator *pMediator)
}

骨感就在于,具体的中介者可以由客户端Client的某些以来类来充当,但是,谁来构建抽象中介者接口呢,她放在底层模块、上层模块都不靠谱,放在客户端更加的不靠谱(你总不能让全部具体模块还持有一个莫须有的抽象的客户端接口类吧)。且这里涉及到编译顺序问题,抽象中介者必须要放在一个能被相关模块库都使用的单独动态库中才行…
然后,然后我就陷入了,后边的“幻想”章节,思考到头疼…

第三者的作用

其实方案二、方案三,它们的共同特点是,利用了第三者,虽然“小三”在方案2可以叫做客户端(想了解客户端在设计模式中的左右可参考X),在方案3叫做中介者,但本质上它们的作用都可称为广义中介。它们类关系图如下,它们都是最精简版的解耦方式。

方案2基本关系图(图100)方案3基本关系图(图200)
在这里插入图片描述在这里插入图片描述

对于图100,当系统所有的运行流程控制都在C中时,这种连接方式相对挺简单的,顺序的执行AB的setData、Run、GetData即可。但若是AB代表模块,且它们持有独立的运行流程控制,再假设没有Qt框架信号槽的支持,再假设你也不愿意形成方案1那样的模块耦合,那你怎办?Here我们继续使用上一节的场景,代码大约如下:

class ClientComm2DServer
{
public:
	void InitModuleConnect();
private:
	static void ProcessComm2DServer(DATA data);
	static void ProcessDServer2Comm(DATA data)
private:
	COMM    *m_pMoudleCOMM;
	DSERVER *m_pMoudleDSERVER;	
}
class COMM 
{
public:
	void GetCommOutputFunc(FuncCommOut pOutputFunc);
private:
	FuncCommOut m_pFuncCommOut;
}
class DSERVER
{
public:
	void GetDServerOutputFunc(FuncDServerOut pOutputFunc);
private:
	FuncDServerOut m_pFuncDServerOut;
}
void ClientComm2DServer::InitModuleConnect()
{
	m_pMoudleCOMM->GetCommOutputFunc(ProcessComm2DServer);
	m_pMoudleDSERVER->GetDServerOutputFunc(ProcessDServer2Comm);
}
void ClientComm2DServer::ProcessComm2DServer(DATA data)  //static
{
	pThis->m_pMoudleDSERVER->DServerInPutAppData(data);
}
void ClientComm2DServer::ProcessDServer2Comm(DATA data)  //static
{
	pThis->m_pMoudleCOMM->CommInputAppData(data);
}

从上边代码来量来看的话,这个中转代码量与方案2的基本一致,原理也一致,就是把模块各自的输出需求转移到持有它们的第三者对象中,然后在转移函数中调用交互方对象的输入接口。从此大约get到不管是模块设计还是类设计,第三者模块或类都挺重要的,它减少了原配间的依赖情节,让他们各奔东西自由飞翔。为了更好的实现模块间的通信,我们在进行模块范畴定义时,要时刻关注两个存在联系的模块他们之间的接口本质是什么,且让接口们保持各自喜欢的样子,然后让第三者去适配它们。接口上清晰了,整个模块结构和系统结构都会明朗起来。

幻想

这个幻想是在思考上述方案三时进入的,非本人可忽略此部分,这只是我的些胡思乱想。那一刻我想起来"软总线",甚至有一瞬间我感觉好像充分利用中介者模式就能立马实现一个简单版本的‘本地模块间通信软总线’,利用它实现各个独立模块间的数据通信。
可以认为总线是一个抽象中介者,各个模块都关联它(如持有它的引用),这个抽象有一个主要的接口 SendMsgAToB(Data, MoudleA, MoudleB) 。由于每个模块都向软总线注册,故具体的总线实现(具体中介者)是持有全部模块对象引用,每个模块都有一对输入输出抽象函数,OutputData(data);InputData(data),假如有U/V/W三个模块,我们可以这么调用
在B的OutputData函数中
语言描述真的是很费劲,还是直接上UML图和代码吧!

class AbstractMediator
{
	virtual bool SendMsgToModuleX(DATA data, MOUDLE moudle);
	virtual bool GetMsgFromModuleX(DATA &data, MOUDLE moudle);
}

class MoudleX(AbstractMediator *pMediator)
{
public:
	virtual bool InputData(DATA data);
	virtual bool OutputData(DATA data);
private:
}
/**
U/V/W 模块从MoudleX类继承
**/
V::OutputData(data)
{
	m_pMediator->SendMsgToModuleX(data, "ModuleWName");
}

一个想象中的简单软总线的定义,从如何解决方案3编译问题出发,抽象中介者必须放到一个能被全部相关模块所引用的动态库中,而这个总线动态库可以简单到只包含一些抽象中介者的接口,允许将这些接口分散到不同的接口类中。

若扩展到同一机器进程间的通信,那么可以让各个进程通过管道等分别与总线进程通信。若扩展到网络间服务通信,则每个机器都启动一个总线进程,使她充当经纪人,让她除了具有本机数据中转功能外,为其扩展网络通信功能即可。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值