1. 程式人生 > >【C++11】物件訊息匯流排(2)

【C++11】物件訊息匯流排(2)

部分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註冊了回覆主題,在執行完成後,通知訊息匯流排即可實現“回覆”功能。

思考

其實我們可將訊息匯流排和狀態機結合一下,當處於某種狀態執行某些操作,然後此操作完成後可以選擇繼續執行某些操作,這些操作可以是不同的主題型別。

訊息匯流排將複雜物件關係簡化了,降低了物件關係的複雜度和耦合度,但是也要注意訊息匯流排的使用場景,物件關係很簡單的時候使用,反而將系統變的很複雜,在物件關係多且複雜的場景,訊息匯流排才能更好的發揮作用。