一、概述
說起觀察者模式,也是比較簡單的一種模式了,稍微工作有1年經驗的同學,寫起來都是666...
想看觀察者模式的說明可以直接上菜鳥教程|觀察者模式這個地址去看。
本篇文章其實就是一個簡單的觀察者模式,只是使用了模板的方式,把我們的回撥介面進行了引數化,這樣有什麼好處呢?
好處當然是大大的有了。 平時我們在不同業務邏輯之間寫觀察者模式呢,都得寫好多個,大家有沒有發現,所有的被觀察者Subject其實很多操作都是一樣的。
本篇我們帶來兩種觀察者模式:同步觀察者和非同步觀察者
1、同步觀察者
顧名思義,同步觀察者其實就是不管是誰,觸發了Subject的Update操作,該操作都是同步進行的,他會呼叫所有的觀察者(Observer)的OnUpdate介面,來通知Observer處理改變操作。
如效果展示圖中的第一個單次拉取
頁籤,當我們點選拉取按鈕時,就相當於觸發了一次Subject物件的Update操作
2、非同步觀察者
非同步觀察者模式上和同步觀察者基本一樣,只是在事件處理上有稍微不同
- 執行Update操作是由Subject自己去完成的
- 呼叫Observer的OnUpdate回撥介面時,處於工作執行緒中
- 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、獲取觀察者指標**
通過一個門面介面獲取觀察者指標
- 呼叫ISignal的Attach介面,就可以把自己新增到觀察者列表。
- 呼叫ISignal的RequestData介面,就可以拉取資料。
- 呼叫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;
};
通過以上四步,就可以很方便的實現一個現在業務中的觀察者,是不是很簡單呢,編寫過程中,需要完成這幾個地方
- 需要定義我們回撥函式的引數結構
- 需要例項化一個被觀察者介面類
- 例項化一個業務觀察者
- 做一個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介面
定時回撥功能測試介面
- on_pushButton_clicked槽函式只是為了把當前執行緒喚醒,並定時回撥
- 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);
}
以上講解就是我們觀察者的實現了,如果有疑問歡迎提出
五、相關文章
如果您覺得文章不錯,不妨給個打賞,寫作不易,感謝各位的支援。您的支援是我最大的動力,謝謝!!!
很重要--轉載宣告
本站文章無特別說明,皆為原創,版權所有,轉載時請用連結的方式,給出原文出處。同時寫上原作者:朝十晚八 or Twowords
如要轉載,請原文轉載,如在轉載時修改本文,請事先告知,謝絕在轉載時通過修改本文達到有利於轉載者的目的。