本系列文档从属于:C++11应用实践系列
部分Demo和结论引用自<深入应用C++11代码优化与工程>这本书
前一节已经介绍了,如何承载消息,本节将会实现,消息保存和分发。
- 消息保存:由于消息类型有不同的返回值以及入参,这里可以使用之前介绍的Any作为消息载体
- 消息分发:这个就比较容易了,取出合适的函数对象(消息)直接调用即可。
消息总线设计思想
消息总线融和了观察者和中介者模式,通过类型擦除擦除具体消息类型(Any)。观察者模式用来维护主题和适当的时候向观察者广播消息。中介者模式主要用来降低观察者模式互相依赖产生的耦合性。
消息总线维护的消息体是所有类型的可调用对象,没有对象之间的直接调用,更没有接口继承,主题和观察者仅仅是通过某种类型的消息联系起来,这个消息就是简单的返回值类型和入参。
下图是消息总线时序图,很好的说明的消息总线工作原理(来自原书)
其中主要的几个点就是:
- 观察者注册消息体
- 消息总线保存消息体
- 主题对象发送消息
- 消息总线调用合适主题的消息体
- 观察者收到消息并处理
完整的消息总线
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注册了回复主题,在执行完成后,通知消息总线即可实现“回复”功能。
思考
其实我们可将消息总线和状态机结合一下,当处于某种状态执行某些操作,然后此操作完成后可以选择继续执行某些操作,这些操作可以是不同的主题类型。
消息总线将复杂对象关系简化了,降低了对象关系的复杂度和耦合度,但是也要注意消息总线的使用场景,对象关系很简单的时候使用,反而将系统变的很复杂,在对象关系多且复杂的场景,消息总线才能更好的发挥作用。