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

C 語言實現觀察者模式:基於委托/事件機制的完整框架代碼解析

     編輯:關於C

專案背景

在純 C 語言中實現《大話設計模式》中的**觀察者模式**,採用**委托/事件**機制:主題(Subject)維護一個回調函數列表(委托),狀態變更時遍歷呼叫所有觀察者回調。無 C++ 虛函數/繼承,僅用函數指標、結構體、手動內存管理實現完整解耦。

核心架構圖

[Boss (Subject)] ──擁有──→ [Entrust (委托/事件中心)] ──管理──→ [List (回調函數陣列)]
      │                                                    ▲
      │ 通知 (Notify)                                      │ 註冊/移除 (Add/Remove)
      ▼                                                    │
[StockObserver] ◄── 回調 (CloseStock) ◄────────────────────┘
[NBAObserver]   ◄── 回調 (CloseNBA)

關鍵資料結構

1. 動態陣列 List(存儲回調函數指標)

typedef struct _List {
    void **items;      // 函數指標陣列
    int count;         // 當前數量
    void (*Add)(struct _List*, void* func);
    void (*Remove)(struct _List*, void* func);
    void (*Delete)(void* list);
} List;
  • Add:重新分配擴容陣列,複製舊項,追加新函數指標,去重。
  • Remove:重新分配縮容陣列,跳過目標函數指標。
  • Delete:釋放陣列與 List 結構體本身。

2. 委托類 Entrust(事件多播器)


typedef void (*Event)(void*); // 回調簽名:接收觀察者 this 指標

typedef struct _Entrust {
    List *funcList;
    void (*Add)(struct _Entrust*, void* func);
    void (*Remove)(struct _Entrust*, void* func);
    void (*Update)(struct _Entrust*); // 觸發所有回調
    void (*Delete)(void* entrust);
} Entrust;
  • Update:遍歷 `funcList`,將每個 `void*` 轉為 `Event` 呼叫:
    `((Event)funcPtr)(funcPtr);` — 傳入函數指標自身作為 `this`(觀察者實例)。

3. 觀察者基礎結構(兩種具體觀察者)


// 看股票同事
typedef struct _StockObserver {
    char *name;
    Boss *subject;
    void (*CloseStock)(void* this); // 回調函數
    void (*Delete)(void* this);
} StockObserver;

// 回調實現
void CloseStock(void* this) {
    StockObserver *self = (StockObserver*)this; // 此處用 ClassEntry 宏更安全
    printf("%s, %s 關閉股票行情,繼續工作\n", 
           self->subject->GetSubjectState(self->subject), self->name);
}

NBAObserver 結構類似,回調為 `CloseNBA`。

4. 主題:Boss(老板)


typedef struct _Boss {
    Entrust *entrust;
    char *action; // 狀態
    char* (*GetSubjectState)(void* this);
    void (*SetSubjectState)(void* this, char* state);
    void (*Notify)(void* this);
    void (*Delete)(void* this);
} Boss;
  • Notify:呼叫 `entrust->Update(entrust)` 觸發廣播。

關鍵技術細節

ClassEntry 宏:從成員指標反推結構體基址


#define ClassEntry(ptr, type, member) \
    ((type*)((char*)(ptr) - (unsigned long)(&((type*)0)->member)))

用途:回調函數接收 `void* this`(實為觀察者的成員函數指標),需反推觀察者結構體指標以存取 `name`、`subject` 等欄位。此宏為 C 語言實現「this 指標」的核心技巧。

內存管理:rt-thread 分配器

代碼使用 `rt_malloc`/`rt_free`(RT-Thread OS API),標準 C 環境下替換為 `malloc`/`free` 即可。

客戶端使用範例


void EntrustObs(void) {
    Boss *huhasan = BossCreate(sizeof(Boss));
    StockObserver *wei = StockObserverCreate("魏關姹", huhasan, sizeof(StockObserver));
    NBAObserver *yi = NBAObserverCreate("易管查", huhasan, sizeof(NBAObserver));

    // 註冊觀察者回調到委托
    huhasan->entrust->Add(huhasan->entrust, &(wei->CloseStock));
    huhasan->entrust->Add(huhasan->entrust, &(yi->CloseNBA));

    // 狀態變更並通知
    huhasan->SetSubjectState(huhasan, "我胡漢三回來啦");
    huhasan->Notify(huhasan);

    // 清理
    huhasan->Delete(huhasan);
    wei->Delete(wei);
    yi->Delete(yi);
}

輸出結果

我胡漢三回來啦,魏關姹 關閉股票行情,繼續工作
我胡漢三回來啦,易管查 關閉NBA轉播,繼續工作

設計優缺點分析

改進建議

  1. 改用**單向鏈表**實現 List,O(1) 插入刪除,無需重分配。
  2. 引入**類型安全回調封裝**:定義具體觀察者介面結構體,避免 `void*` 濫用。
  3. 增加**線程安全**:List 操作加互斥鎖,支援多執行緒環境。
  4. 使用 **C11 `_Generic`** 或編譯器擴展做有限度類型檢查。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved
優點缺點
純 C 實現,無 C++ 依賴,可用於嵌入式/內核手動內存管理易洩漏,需嚴格遵循 Create/Delete 配對
委托機制解耦主題與觀察者,支援動態增減函數指標轉型不安全,編譯器無法檢查簽名匹配
ClassEntry 宏實現「this」語義,接近 OOP 體驗宏依賴結構體佈局,修改結構體順序易導致錯誤
List 動態陣列自動擴縮,使用簡便頻繁 Add/Remove 會重複分配記憶體,效率較低