應用開發過程中經常會處理對象間通信的問題,一般都是對象或接口的依賴和引用去實現對象間的通信,這在一般情況下是沒問題的,但是如果相互通信的對象很多,可能會造成對象間的引用關系像蜘蛛網一樣,這樣會導致對象關系很復雜,難以維護的問題,解決這個問題的一個好方法是通過消息總線去解耦對象間大量相互引用的緊耦合的關系。
設計思路:被通信對象向消息總線發布一個主題,這個主題包含消息主題、消息類型和消息處理函數,消息主題標示某個特定的主題,消息類型用來區分標示這個主題會響應某個特定的消息,消息處理函數用來響應該主題的某種消息類型。通信對象向消息總線發送某個特定主和某個特定消息,總線就會根據消息主題和消息類型找到對應的消息處理函數處理該請求。
由於用到了c++11的可變模板參數和lamda表達式,windows上編譯需要Compiler Nov 2012 CTP,linux需要GCC4.7以上。
具體代碼:
#pragma once
#include <boost/tuple/tuple.hpp>
#include <boost/utility.hpp>
#include <boost/unordered_map.hpp>
#include <boost/any.hpp>
template <typename... Args>
struct Impl;
template <typename First, typename... Args>
struct Impl<First, Args...>
{
static std::string name()
{
return std::string(typeid(First).name()) + " " + Impl<Args...>::name();
}
};
template <>
struct Impl<>
{
static std::string name()
{
return "";
}
};
template <typename... Args>
std::string type_name()
{
return Impl<Args...>::name();
}
class MessageBus : boost::noncopyable
{
public:
//向某個主題注冊主題,需要訂閱主題(topic、消息類型)和消息處理函數。
template<typename... TArgs, typename TObject, typename TMember>
void Attach(string strTopic, TObject* Object, TMember Member)
{
std::function<void(TArgs...)> f = std::function<void(TArgs...)>([=](TArgs... arg){(Object->*Member)(arg...);});
m_map.insert(make_pair(GetKey(strTopic), f));
}
//向某個主題發送消息, 需要主題和消息類型。消息總線收到消息後會找到並通知對應的消息處理函數。
template<typename... Args>
void SendReq(string strTopic, Args... args)
{
auto range=m_map.equal_range(GetKey(strTopic));
boost::unordered_multimap<string, boost::any>::iterator it;
for (it = range.first; it!= range.second; it++)
{
std::function<void(Args...)> f = boost::any_cast<std::function<void(Args...)>>(it->second);
f(args...);
}
}
//移除某個主題, 需要主題和消息類型
template<typename... Args>
void Remove(string strTopic)
{
auto it = m_map.find(GetKey(strTopic));
while(it!=m_map.end())
m_map.erase(it++);
}
private:
//獲得消息鍵值,通過某個主題和消息類型可以確定觀察者
template<typename... TArgs>
string GetKey(string& strTopic)
{
return strTopic + type_name<TArgs...>();
}
private:
boost::unordered_multimap<string, boost::any> m_map;
};
測試代碼:
MessageBus bus;
MyStruct st;
bus.Attach<int,string>("bb", &st, &MyStruct::Test); //注冊主題(topic、消息類型、消息處理函數)
bus.Attach<int,string>("bb", &st, &MyStruct::Test2);
bus.SendReq<int, string>("bb",0," append"); //發送消息處理請求(主題和消息類型)
bus.Remove<int, string>("bb"); //移除主題(主題和消息類型)
測試結果:
it is a test: 0 append
it is a test2: 0 append
更新版本,通過萬能的函數包裝器實現消息總線,使得接口的調用更加通用和一致。
template <typename R=void>
class MessageBus : boost::noncopyable
{
public:
//注冊消息
template< class... Args, class F, class = typename std::enable_if<!std::is_member_function_pointer<F>::value>::type>
void Attach(string strKey, F && f)
{
std::function<R(Args...)> fn = [&](Args... args){return f(std::forward<Args>(args)...); };
m_map.insert(std::make_pair(strKey + type_name < Args...>(), std::move(fn)));
}
// non-const member function
template<class... Args, class C, class... DArgs, class P>
void Attach(string strKey, R(C::*f)(DArgs...), P && p)
{
std::function<R(Args...)> fn = [&, f](Args... args){return (*p.*f)(std::forward<Args>(args)...); };
m_map.insert(std::make_pair(strKey + type_name < Args...>(), std::move(fn)));
}
template<class... Args, class C, class... DArgs, class P>
void Attach(string strKey, R(C::*f)(DArgs...) const, P && p)
{
std::function<R(Args...)> fn = [&, f](Args... args){return (*p.*f)(std::forward<Args>(args)...); };
m_map.insert(std::make_pair(strKey + type_name < Args...>(), std::move(fn)));
}
//廣播消息,主題和參數可以確定一個消息, 所有的消息接收者都將收到並處理該消息
template<typename... Args>
void SendReq(string strTopic, Args... args)
{
auto range = m_map.equal_range(strTopic + type_name < Args...>());
for (auto it = range.first; it != range.second; it++)
{
std::function<R(Args...)> f = boost::any_cast<std::function<R(Args...)>>(it->second);
f(args...);
}
}
//移除消息
template<typename... Args>
void Remove(string strTopic)
{
string strMsgType = GetNameofMsgType<Args...>();
auto range=m_map.equal_range(strTopic+strMsgType);
m_map.erase(range.first, range.second);
}
private:
std::multimap<string, boost::any> m_map;
};
測試代碼:
struct A
{
void Test(int x){ cout << x << endl; }
void GTest()
{
cout << "it is a test" << endl;
}
void HTest(int x) const
{
cout << "it is a HTest" << endl;
}
};
void GG(int x)
{
cout << "it is a gg" << endl;
}
void GG1()
{
cout << "it is a GG" << endl;
}
void TestMessageBus()
{
A a;
MessageBus<> bus;
bus.Attach<int>("aa", &A::Test, &a);
int x = 3;
bus.SendReq("aa", 3);
bus.Attach<int>("hh", &A::HTest, &a);
bus.SendReq("hh", x);
bus.Attach("bb", &A::GTest, &a);
bus.SendReq("bb");
bus.Attach<int>("gg", GG);
bus.SendReq("gg", 3);
bus.Attach("gg", GG1);
bus.SendReq("gg");
}