C++屌屌的觀察者模式-同步回調和異步回調


原文鏈接:C++屌屌的觀察者模式-同步回調和異步回調

一、概述

說起觀察者模式,也是比較簡單的一種模式了,稍微工作有1年經驗的同學,寫起來都是666...

想看觀察者模式的說明可以直接上菜鳥教程|觀察者模式這個地址去看。

本篇文章其實就是一個簡單的觀察者模式,只是使用了模板的方式,把我們的回調接口進行了參數化,這樣有什么好處呢?

好處當然是大大的有了。 平時我們在不同業務邏輯之間寫觀察者模式呢,都得寫好多個,大家有沒有發現,所有的被觀察者Subject其實很多操作都是一樣的。

本篇我們帶來兩種觀察者模式:同步觀察者和異步觀察者

1、同步觀察者

顧名思義,同步觀察者其實就是不管是誰,觸發了Subject的Update操作,該操作都是同步進行的,他會調用所有的觀察者(Observer)的OnUpdate接口,來通知Observer處理改變操作。

如效果展示圖中的第一個單次拉取頁簽,當我們點擊拉取按鈕時,就相當於觸發了一次Subject對象的Update操作

2、異步觀察者

異步觀察者模式上和同步觀察者基本一樣,只是在事件處理上有稍微不同

  1. 執行Update操作是由Subject自己去完成的
  2. 調用Observer的OnUpdate回調接口時,處於工作線程中
  3. Subject所有的請求操作都是在工作現場中進行

如效果圖所示,定時拉取觀察者模式,Subject啟動了一個后台線程,3秒鍾拉取一次數據,並回調到界面

二、效果展示

如下圖所示,是一個簡單的觀察者模式事例。

單次拉取:演示了同步觀察者模式

定時拉取:演示了異步觀察者模式

工程結構如圖所示,這里只把頭文件的目錄展示出來了。

實現文件的目錄和頭文件類似,為了截圖方便所以做了隱藏操作。

Header Files目錄下有2個虛擬文件夾,分別就是對單次拉取定時拉取功能的實踐

下面我們就來正式開始講解這個屌屌的觀察者模式

三、同步觀察者

1、首先就是定義一堆接口和回調參數

struct DataItem
{
	std::string		strID;	
	std::string		strName;		
};

typedef IUpdate1<DataItem>			ISignalObserver;

//單次回調
struct ISignal : public SubjectBase<ISignalObserver>
{
	virtual void RequestData() = 0;
};

2、業務觀察者

這里我定義了一個SignalResponse業務觀察者,也就是我們在開發工程中的實際功能類。

class SignalResponse : public ISignal
{
public:
	SignalResponse();
	~SignalResponse();

public:
	virtual void RequestData() override;

private:
	
};

3、獲取觀察者指針*

通過一個門面接口獲取觀察者指針

  1. 調用ISignal的Attach接口,就可以把自己添加到觀察者列表。
  2. 調用ISignal的RequestData接口,就可以拉取數據。
  3. 調用ISignal的Detach接口,就可以把自己從觀察者列表中移除。
ISignal * GetSignalCommon();

4、UI界面

接下來就是寫一個UI界面啦,當我們通過上一步調用拉取數據接口后,我們的UI上相應的OnUpdate接口就會被回調

class SignalWidget : public QWidget, public ISignalObserver
{
	Q_OBJECT

public:
	SignalWidget(QWidget * parent = 0);
	~SignalWidget();

protected:
	virtual void OnUpdate(const DataItem &) override;

private slots:
	void on_pushButton_clicked();

private:
	Ui::SignalWidget *ui;
};

通過以上四步,就可以很方便的實現一個現在業務中的觀察者,是不是很簡單呢,編寫過程中,需要完成這幾個地方

  1. 需要定義我們回調函數的參數結構
  2. 需要實例化一個被觀察者接口類
  3. 實例化一個業務觀察者
  4. 做一個UI界面,並集成第二步實例化的被觀察者的模板參數(接口類)

注意看這里的ISignalObserver,是不是很眼熟,其實他就是我們的模板被觀察者SubjectBase的模板參數。

講到這里,大家是不是都很關心這個模板觀察者到底是何方神聖,居然這么叼。那么接下來就是模板SubjectBase出場啦。。。

下面我直接給出代碼,學過C++的同學閱讀起來應該都不難。

覺着難了就多讀幾遍

template <typename T>
struct ISubject
{
	virtual void Attach(T * pObserver) = 0;
	virtual void Detach(T * pObserver) = 0;
};

template <typename P>
struct IUpdate1
{
	virtual void OnUpdate(const P& data) = 0;
};

template <typename P1, typename P2>
struct IUpdate2
{
	virtual void OnUpdate2(const P1 & p1, const P2 & p2) = 0;
};

template <typename P>
struct IUpdate1_P
{
	virtual void OnUpdate(const P * data) = 0;
};

template <typename T>
struct SubjectBase
{
public:
	virtual void Attach(T * pObserver)
	{
		std::lock_guard<std::mutex> lg(m_mutex);
#ifdef _DEBUG
		if (m_observers.end() != std::find(m_observers.begin(), m_observers.end(), pObserver))
		{
			assert(false);
		}
#endif // _DEBUG
		m_observers.push_back(pObserver);
	}

	virtual void Detach(T * pObserver)
	{
		std::lock_guard<std::mutex> lg(m_mutex);
		auto it = std::find(m_observers.begin(), m_observers.end(), pObserver);
		if (it != m_observers.end())
		{
			m_observers.erase(it);
		}
		else
		{
			assert(false);
		}
	}

	//protected:
	template <typename P>
	void UpdateImpl(const P & data)
	{
		std::lock_guard<mutex> lg(m_mutex);
		for (T * observer : m_observers)
		{
			observer->OnUpdate(data);
		}
	}

	template <typename P>
	void UpdateImpl(P & data)
	{
		std::lock_guard<std::mutex> lg(m_mutex);
		for (T* observer : m_observers)
		{
			observer->OnUpdate(data);
		}
	}

	template <typename P1, typename P2>
	void UpdateImpl(const P1& p1, const P2& p2)
	{
		std::lock_guard<mutex> lg(m_mutex);
		for (T* observer : m_observers)
		{
			observer->OnUpdate2(p1, p2);
		}
	}

	template <typename P1, typename P2>
	void UpdateImpl(P1& p1, P2& p2)
	{
		std::lock_guard<mutex> lg(m_mutex);
		for (T* observer : m_observers)
		{
			observer->OnUpdate2(p1, p2);
		}
	}

	template <typename P>
	void UpdateImpl(const P * data)
	{
		std::lock_guard<mutex> lg(m_mutex);
		for (T * observer : m_observers)
		{
			observer->OnUpdate(data);
		}
	}

	template <typename P>
	void UpdateImpl(P * data)
	{
		std::lock_guard<mutex> lg(m_mutex);
		for (T* observer : m_observers)
		{
			observer->OnUpdate(data);
		}
	}

protected:
	std::mutex		m_mutex;
	std::list<T *>	m_observers;
};

四、異步觀察者

異步觀察者的實現和同步觀察者的結構基本一樣,都是使用同樣的套路,唯一有區別的地方就是,異步觀察者所有的邏輯處理操作都是在工作線程中的。

由於ITimerSubject和SubjectBase很多接口都是一樣的,因此我這里就只把差異的部分貼出來。

1、線程

ITimerSubject對象在構造時,就啟動了一個線程,然后在線程中定時執行TimerNotify函數

ITimerSubject()
{
	m_thread = std::thread(std::bind(&ITimerSubject::TimerNotify, this));
}

virtual ~ITimerSubject()
{
	m_thread.join();
}

再來看下定時處理任務這個函數,這個函數本身是用boost的庫實現我的,我改成C++11的模式的,新城退出這塊有些問題,我沒有處理,這個也不是本篇文章的核心要講解的東西。

怎么優雅的退出std::thread,這個從網上查下資料吧,我能想到的也就是加一個標識,然后子線程去判斷。如果大家有更好的辦法的話可以私信我,或者在底部留言。

void TimerNotify()
{
	for (;;)
	{
		//std::this_thread::interruption_point();

		bool bNotify = false;
		{
			std::lock_guard<std::mutex> lg(m_mutex);
			bNotify = m_sleeping_observers.size() < m_observers.size() ? true : false;
		}

		if (bNotify)
		{
			OnTimerNotify();
		}

		//std::this_thread::interruption_point();

		std::chrono::milliseconds timespan(GetTimerInterval() * 1000); // or whatever
		std::this_thread::sleep_for(timespan);
	}
}

2、定義一堆接口和回調參數

struct TimerDataItem
{
	std::string		strID;
	std::string		strName;
};

typedef IUpdate1<TimerDataItem>		ITimerObserver;

//定時回調
struct ITimer : public ITimerSubject<ITimerObserver, std::string, TimerDataItem>{};

3、業務觀察者

這里我定義了一個TimerResponse業務觀察者,也就是我們在開發工程中的實際功能類。

class TimerResponse : public ITimer
{
public:
	TimerResponse();
	~TimerResponse();

protected:
	virtual void OnNotify() override;

private:
	
};

TimerResponse::OnNotify()這個接口的實現就像這樣,這里需要注意的一點是,這個函數的執行位於工作線程中,也就意味着UI界面的回調函數也在工作線程中,操作UI界面時,一定需要拋事件到UI線程中。

void TimerResponse::OnNotify()
{
	static int id = 0;
	static std::string name = "miki";
	id += 1;
	TimerDataItem item;

	std::stringstream ss;
	ss << "timer" << id;

	item.strID = ss.str();
	item.strName = name;

	UpdateImpl(item);
}

OnNotify會定時被調用,然后去更新UI上的內容。

4、獲取觀察者指針

通過一個門面接口獲取觀察者指針,調用ITimer的Attach接口把自己添加到觀察者列表,然后就可以定時獲取到數據,反之也能把自己從觀察者列表中移除,並停止接收到數據。

ITimer * GetTimerCommon();

5、UI界面

定時回調功能測試界面

  1. on_pushButton_clicked槽函數只是為了把當前線程喚醒,並定時回調
  2. OnUpdate屬於定時回調接口
class TimerWidget : public QWidget, public ITimerObserver
{
	Q_OBJECT

public:
	TimerWidget(QWidget *parent = 0);
	~TimerWidget();

protected:
	virtual void OnUpdate(const TimerDataItem &) override;

private slots:
	void on_pushButton_clicked();

signals:
	void RerfushData(TimerDataItem);

private:
	Ui::TimerWidget *ui;
};

上邊也強調過了,OnUpdate的執行是在工作線程中的,因此實現的時候,如果涉及到訪問UI界面,一定要注意切換線程

void TimerWidget::OnUpdate(const TimerDataItem & item)
{
	//注意這里的定時回調都在工作線程中 需要切換到主線程

	emit RerfushData(item);
}

以上講解就是我們觀察者的實現了,如果有疑問歡迎提出

五、相關文章

菜鳥教程|觀察者模式


如果您覺得文章不錯,不妨給個 打賞,寫作不易,感謝各位的支持。您的支持是我最大的動力,謝謝!!!




很重要--轉載聲明

  1. 本站文章無特別說明,皆為原創,版權所有,轉載時請用鏈接的方式,給出原文出處。同時寫上原作者:朝十晚八 or Twowords

  2. 如要轉載,請原文轉載,如在轉載時修改本文,請事先告知,謝絕在轉載時通過修改本文達到有利於轉載者的目的。



免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM