首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> C++ >> 關於C++

觀察者模式 C++ 通用實作(三):複合主題、多執行緒安全與通知期修改防護

     編輯:關於C++

承接前兩篇,本文探討實務專案中遇到的三大棘手問題:複合主題(一個主題對應多種觀察者介面)、多執行緒環境下的執行緒安全、以及觀察者在更新通知期間修改觀察者清單導致迭代器失效的問題。

一、複合主題:多介面觀察者支援

GUI 元件常需同時支援滑鼠、鍵盤等多種事件監聽器。利用多重繼承或組合技術,讓 Widget 同時扮演多個主題角色:

// 多重繼承版
class Widget : protected BasicSubject<MouseListener>, protected BasicSubject<KeyListener> {
    typedef BasicSubject<MouseListener> MouseSubject;
    typedef BasicSubject<KeyListener> KeySubject;
public:
    inline void addMouseListener(MouseListener& mel) { MouseSubject::addObserver(mel); }
    inline void removeMouseListener(MouseListener& mel) { MouseSubject::removeObserver(mel); }
    inline void addKeyListener(KeyListener& kel) { KeySubject::addObserver(kel); }
    inline void removeKeyListener(KeyListener& kel) { KeySubject::removeObserver(kel); }
    void handleMsg(int msg) {
        if (msg == 0) MouseSubject::notifyAll(&MouseListener::mouseMoved, 1, 1);
        else if (msg == 1) KeySubject::notifyAll(&KeyListener::keyPressed, 100);
    }
};

// 組合版
class Widget {
    BasicSubject<MouseListener> ms_;
    BasicSubject<KeyListener> ks_;
public:
    inline void addMouseListener(MouseListener& mel) { ms_.addObserver(mel); }
    // ...
};

使用 protected 繼承隱藏基底介面,對外提供友善的 addXxxListener 方法。

二、多執行緒安全:Policy-based Locking

引入 Policy 設計模式,預設無鎖(單執行緒),可切換至 Critical Section(Windows 程序內鎖)或其他鎖實作:

struct NullLocker { inline void lock() {} inline void unlock() {} };
struct CriticalSectionLocker {
    CriticalSectionLocker() { InitializeCriticalSection(&cs_); }
    ~CriticalSectionLocker() { DeleteCriticalSection(&cs_); }
    inline void lock() { EnterCriticalSection(&cs_); }
    inline void unlock() { LeaveCriticalSection(&cs_); }
private: CRITICAL_SECTION cs_;
};

template <class ObserverT, class LockerT = NullLocker, class ContainerT = std::list<ObserverT*>>
class BasicSubject : protected LockerT {
public:
    void addObserver(ObserverT& o) { lock(); observers_.push_back(&o); unlock(); }
    void removeObserver(ObserverT& o) { lock(); /* erase */ unlock(); }
    template <typename ReturnT>
    void notifyAll(ReturnT (ObserverT::*pfn)()) {
        lock();
        for (auto it = observers_.begin(); it != observers_.end(); ++it) ((*it)->*pfn)();
        unlock();
    }
protected: ContainerT observers_;
};
// 使用:class Widget : protected BasicSubject<MouseListener, CriticalSectionLocker> { ... };

三、通知期間修改觀察者清單:延遲修改緩衝區

若觀察者在 update 中增刪觀察者,會導致正在迭代的容器結構改變,迭代器失效。鎖無法解決此問題(同一執行緒內)。解法:迭代期間將修改動作記錄至緩衝區,迭代結束後重放:

class BasicSubject {
    bool withinLoop_ = false;
    std::multimap<bool, ObserverT*> modifyActionBuf_; // true=add, false=remove
public:
    void addObserver(ObserverT& o) {
        lock();
        if (withinLoop_) modifyActionBuf_.insert({true, &o});
        else observers_.push_back(&o);
        unlock();
    }
    void removeObserver(ObserverT& o) {
        lock();
        if (withinLoop_) modifyActionBuf_.insert({false, &o});
        else observers_.erase(remove(observers_.begin(), observers_.end(), &o), observers_.end());
        unlock();
    }
protected:
    template <typename ReturnT>
    void notifyAll(ReturnT (ObserverT::*pfn)()) {
        beginLoop();
        for (auto it = observers_.begin(); it != observers_.end(); ++it) ((*it)->*pfn)();
        endLoop();
    }
private:
    void beginLoop() { lock(); withinLoop_ = true; unlock(); }
    void endLoop() {
        lock();
        if (!modifyActionBuf_.empty()) {
            for (auto& act : modifyActionBuf_) {
                if (act.first) observers_.push_back(act.second);
                else observers_.erase(remove(observers_.begin(), observers_.end(), act.second), observers_.end());
            }
            modifyActionBuf_.clear();
        }
        withinLoop_ = false;
        unlock();
    }
};

使用 multimap 記錄動作類型與觀察者指標,迭代結束後批次套用,確保迭代器有效性。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved