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

来源:https://www.cnblogs.com/swarmbees/archive/2019/07/10/11155072.html
-Advertisement-
Play Games

[TOC] 原文鏈接: "C++屌屌的觀察者模式 同步回調和非同步回調" 一、概述 說起觀察者模式,也是比較簡單的一種模式了,稍微工作有1年經驗的同學,寫起來都是666... 想看觀察者模式的說明可以直接上 "菜鳥教程|觀察者模式" 這個地址去看。 本篇文章其實就是一個簡單的觀察者模式,只是使用了模板 ...


目錄

原文鏈接: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. 如要轉載,請原文轉載,如在轉載時修改本文,請事先告知,謝絕在轉載時通過修改本文達到有利於轉載者的目的。



您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 架構雜談《一》 從傳統單體架構到服務化架構的發展歷程 典型的單體架構分為三個層級,Web層、業務邏輯層和數據存儲層,每個層的指責分別如下: Web 層:負責與用戶交互或者對外提供介面 業務邏輯層:為了實現業務邏輯而設計的流程處理模塊 數據存儲層:將業務邏輯層處理的結果持久化 將不同的模塊化組件聚合後 ...
  • (詳細)高校宿舍管理系統需求分析說明書(文末-->獲取原文檔) (詳細)高校宿舍管理系統需求分析說明書(文末-->獲取原文檔) 版本狀態 版本 作者 參與者 起止日期 註釋 審閱者 團隊 版本 日期 簽名 教學管理委員會 V1.1 2019.06.13 胡桂虹 教學管理委員會 V1.2 2019.0 ...
  • 1. 什麼是冪等性 冪等性就是指:一個冪等操作任其執行多次所產生的影響均與一次執行的影響相同。用數學的概念表達是這樣的: f(f(x)) = f(x).就像 nx1 = n 一樣, x1 就是一個冪等操作。無論是乘以多少次結果都一樣。 2. 常見的冪等性問題 冪等性問題經常會是由網路問題引起的,還有 ...
  • 分類整理一些內容,方便需要時回過頭來看,整理不易,如有疏漏,請多擔待!之後要查看這篇文章,公眾號後臺回覆 “設計模式聚合” 無靈魂,不模式。 設計模式是什麼鬼(初探) 設計模式是什麼鬼(原型) 設計模式是什麼鬼(單例) 設計模式是什麼鬼(適配器) 設計模式是什麼鬼(策略) 設計模式是什麼鬼(狀態) ...
  • SpringCloud系列教程 | 第九篇:服務網關Zuul初探 Springboot: 2.1.6.RELEASE SpringCloud: Greenwich.SR1 如無特殊說明,本系列教程全採用以上版本 前面的文章我們介紹了,Eureka用於服務的註冊於發現,Feign支持服務的調用以及均衡 ...
  • 目前沒有系統學習過 Spring 框架,參與工作時,直接參与到了 Spring Boot 項目的開發。目前還比較菜,所以,你要是和我一樣,不妨也跳過 Spring 框架的學習,直接學習 Sring Boot。 官方文檔 的一段介紹: Spring Boot makes it easy to crea ...
  • 如果你對以下幾個問題有疑問,那麼本文可能會有所幫助。 1.2.3 談協程繞不開線程,按傳統還得從進程談起,不過我想業內人員對進程和線程應該是耳熟能詳,這裡就簡單概括下。 進程擁有自己獨立的堆和棧,既不共用堆,亦不共用棧,進程由操作系統調度;線程擁有自己獨立的棧,共用堆(也可以有自己的私有域),不共用 ...
  • Java 多線程系列文章第 3 篇 這篇文章繼續來嘮嘮概念,講這三兄弟: 串列(Serial) 、 並行(Parallel) 、 併發(Concurrent) 。 吃快餐 出門在外吃飯是一件頭疼的事,用我大學舍友一句話形容:如果不是沒吃飯不能活,他是不會吃飯的。不管學生還是工作者,吃飯都是一件需要揪 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...