在純 C 語言中實現《大話設計模式》中的**觀察者模式**,採用**委托/事件**機制:主題(Subject)維護一個回調函數列表(委托),狀態變更時遍歷呼叫所有觀察者回調。無 C++ 虛函數/繼承,僅用函數指標、結構體、手動內存管理實現完整解耦。
[Boss (Subject)] ──擁有──→ [Entrust (委托/事件中心)] ──管理──→ [List (回調函數陣列)]
│ ▲
│ 通知 (Notify) │ 註冊/移除 (Add/Remove)
▼ │
[StockObserver] ◄── 回調 (CloseStock) ◄────────────────────┘
[NBAObserver] ◄── 回調 (CloseNBA)
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 結構體本身。
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` 呼叫:
// 看股票同事
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`。
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)` 觸發廣播。
#define ClassEntry(ptr, type, member) \
((type*)((char*)(ptr) - (unsigned long)(&((type*)0)->member)))
用途:回調函數接收 `void* this`(實為觀察者的成員函數指標),需反推觀察者結構體指標以存取 `name`、`subject` 等欄位。此宏為 C 語言實現「this 指標」的核心技巧。
代碼使用 `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轉播,繼續工作
| 優點 | 缺點 |
|---|---|
| 純 C 實現,無 C++ 依賴,可用於嵌入式/內核 | 手動內存管理易洩漏,需嚴格遵循 Create/Delete 配對 |
| 委托機制解耦主題與觀察者,支援動態增減 | 函數指標轉型不安全,編譯器無法檢查簽名匹配 |
| ClassEntry 宏實現「this」語義,接近 OOP 體驗 | 宏依賴結構體佈局,修改結構體順序易導致錯誤 |
| List 動態陣列自動擴縮,使用簡便 | 頻繁 Add/Remove 會重複分配記憶體,效率較低 |