【C++11】物件訊息匯流排(2)
阿新 • • 發佈:2019-02-18
部分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註冊了回覆主題,在執行完成後,通知訊息匯流排即可實現“回覆”功能。
思考
其實我們可將訊息匯流排和狀態機結合一下,當處於某種狀態執行某些操作,然後此操作完成後可以選擇繼續執行某些操作,這些操作可以是不同的主題型別。
訊息匯流排將複雜物件關係簡化了,降低了物件關係的複雜度和耦合度,但是也要注意訊息匯流排的使用場景,物件關係很簡單的時候使用,反而將系統變的很複雜,在物件關係多且複雜的場景,訊息匯流排才能更好的發揮作用。