【C++11】对象消息总线(2)

本系列文档从属于:C++11应用实践系列

部分Demo和结论引用自<深入应用C++11代码优化与工程>这本书


前一节已经介绍了,如何承载消息,本节将会实现,消息保存和分发。

  1. 消息保存:由于消息类型有不同的返回值以及入参,这里可以使用之前介绍的Any作为消息载体
  2. 消息分发:这个就比较容易了,取出合适的函数对象(消息)直接调用即可。

消息总线设计思想

消息总线融和了观察者和中介者模式,通过类型擦除擦除具体消息类型(Any)。观察者模式用来维护主题和适当的时候向观察者广播消息。中介者模式主要用来降低观察者模式互相依赖产生的耦合性。
消息总线维护的消息体是所有类型的可调用对象,没有对象之间的直接调用,更没有接口继承,主题和观察者仅仅是通过某种类型的消息联系起来,这个消息就是简单的返回值类型和入参。

下图是消息总线时序图,很好的说明的消息总线工作原理(来自原书)
在这里插入图片描述
其中主要的几个点就是:

  1. 观察者注册消息体
  2. 消息总线保存消息体
  3. 主题对象发送消息
  4. 消息总线调用合适主题的消息体
  5. 观察者收到消息并处理

完整的消息总线

class MessageBus
{
public:
	//注册消息
	template<typename F>
	void attach(F&& f, const string& topic = "")
	{
		auto func = to_function(std::forward<F>(f));
		add(topic, std::move(func));
	}

	//发送消息 没有参数
	template<typename R>
	void send(const string& topic = "")
	{
		using function_type = std::function<R()>;
		string strMsg = topic + typeid(function_type).name();
		auto range = m_map.equal_range(strMsg);
		for (auto iter = range.first; iter != range.second; ++iter)
		{
			auto f = iter->second.cast<function_type>();
			f();
		}
	}
	//发送消息 有参数
	template<typename R, typename... Args>
	void send(Args&&...args, const string& topic = "")
	{
		using function_type = std::function<R(Args...)>;
		string strMsg = topic + typeid(function_type).name();
		auto range = m_map.equal_range(strMsg);
		for (auto iter = range.first; iter != range.second; ++iter)
		{
			auto f = iter->second.cast<function_type>();
			f(std::forward<Args>(args)...);
		}
	}
	//移除某个主题 需要主题和消息类型
	template<typename R, typename... Args>
	void remove(const string& topic = "")
	{
		using function_type = std::function<R(Args...)>;
		string strMsg = topic + typeid(function_type).name();
		auto range = m_map.equal_range(strMsg);
		m_map.erase(range.first, range.second);
	}

private:
	template<typename F>
	void add(const string& topic, F&& f)
	{
		string strMsg = topic + typeid(F).name();
		m_map.emplace(std::move(strMsg), std::forward<F>(f));
	}

private:
	std::multimap<string, Any> m_map;
};

测试代码如下:


MessageBus g_bus;
string g_topic = "drive";	//主题类型
struct Subject
{
    //主题对象发送某种消息,
	void send(const string& topic)
	{
		g_bus.send<void, int>(50, topic);
	}
};

struct Car
{
	Car()
	{
		//注册到消息总线
		g_bus.attach([this](int speed) {drive(speed); }, g_topic);
	}
	void drive(int speed)
	{
	    //观察者对象接收到某种消息的响应体(lambda封装)
		cout << "car drive:" << speed << endl;
	}
};

struct Bus
{
	Bus()
	{
		//注册到消息总线
		g_bus.attach([this](int speed) {drive(speed); }, g_topic);
	}
	void drive(int speed)
	{
	    //观察者对象接收到某种消息的响应体(lambda封装)
		cout << "bus drive:" << speed << endl;
	}
};

void testBus()
{
	Subject subject;
	Car car;
	Bus bus;
	subject.send(g_topic);
	cout << "---------------" << endl;
	subject.send("");
}

不出所料,观察者Car和Bus接收到主题"drive"消息,由消息体触发drive函数调用。

复杂的对象关系梳理

我们上面实现的仅仅是简单的发送主题,然后响应。如果我们希望,观察者受到消息后,可以进行“回复”,这时候消息总线的优势就更一步体现了,我们只需重新注册一种主题即可。


MessageBus g_bus;
string g_topic = "drive";	//主题类型
string g_callbacktopic = "driveok";
struct Subject
{
	Subject()
	{
		g_bus.attach([this]{driveok(); }, g_callbacktopic);
	}

	void send(const string& topic)
	{
		g_bus.send<void, int>(50, topic);
	}

	void driveok()
	{
		cout << "driveok" << endl;
	}
};

struct Car
{
	Car()
	{
		//注册到消息总线
		g_bus.attach([this](int speed) {drive(speed); }, g_topic);
	}
	void drive(int speed)
	{
		cout << "car drive:" << speed << endl;
		//额外增加处理函数
		g_bus.send<void>(g_callbacktopic);
	}
};

struct Bus
{
	Bus()
	{
		//注册到消息总线
		g_bus.attach([this](int speed) {drive(speed); }, g_topic);
	}
	void drive(int speed)
	{
		cout << "bus drive:" << speed << endl;
	}
};

void testBus()
{
	Subject subject;
	Car car;
	Bus bus;
	subject.send(g_topic);
	cout << "---------------" << endl;
	subject.send("");
}

在这里插入图片描述
可以看到,Car注册了回复主题,在执行完成后,通知消息总线即可实现“回复”功能。

思考

其实我们可将消息总线和状态机结合一下,当处于某种状态执行某些操作,然后此操作完成后可以选择继续执行某些操作,这些操作可以是不同的主题类型。

消息总线将复杂对象关系简化了,降低了对象关系的复杂度和耦合度,但是也要注意消息总线的使用场景,对象关系很简单的时候使用,反而将系统变的很复杂,在对象关系多且复杂的场景,消息总线才能更好的发挥作用。

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值