概述
在沒有面向對象語法的C語言中,策略(Strategy)模式和狀態(State)模式都通過“上下文 + 接口”組合來模擬多態。
它們在代碼結構上幾乎一致,但設計意圖和應用場景卻差異很大。
本文分三部分深入剖析:
- 在C語言中如何模擬多態
- 策略模式與狀態模式的設計意圖與差異
- 結合EEPROM抽象層案例,展示二者協作與狀態機應用
1. C語言中的“模擬多態”
在C語言中,多態可通過結構體與函數指針模擬:上下文(Context)持有一組函數指針(vtable),動態指向具體實現。下例展示通用框架:
// 通用接口類型(vtable)
typedef struct {void (*action)(void *ctx, int evt);
} module_iface_t;// 通用上下文
typedef struct {const module_iface_t *iface; // 指向當前策略或狀態的 vtablevoid *instance_data; // 指向具體策略/狀態實例私有數據
} module_ctx_t;
在策略模式中,action
對應具體算法;在狀態模式中,action
則為事件處理函數。此框架既可用于策略,也可用于狀態,賦予系統極高的復用性。
2. 策略模式 vs. 狀態模式
2.1 策略模式:關注“如何做”
- 目的:封裝一組可互換的算法,讓調用者在運行時自行選擇
- 決策權:在外部,上下文被動執行指定策略
- 應用場景:濾波算法切換、通信協議選擇、后端存儲后備機制等
示例類圖
2.2 狀態模式:關注“何時做、做什么”
- 目的:將對象行為與內部狀態綁定,使行為隨狀態自動切換
- 決策權:在內部,狀態自身決定是否及何時轉換
- 應用場景:設備驅動、協議狀態機、時序復雜的后端服務等
示例類圖與狀態圖
核心差異
- 策略模式:算法平行,可在任意時刻替換
- 狀態模式:狀態有序,轉換邏輯由狀態機管理
2.3 小結:模式賦能架構
在前文中,我們分別闡述了策略模式如何將“如何做”的決策權交給外部,做到算法與調用邏輯的徹底解耦;以及狀態模式如何把“何時做、做什么”的流程控制封裝到內部狀態機中,保障行為隨狀態演進而自動切換。
既然二者在實現上高度相似,我們有時候可以將二者組合起來使用。
3. 實戰:高可靠EEPROM抽象層
下面通過分層設計,展示策略模式與狀態機的協同應用。
3.1 統一層:策略模式應用
需求
為應用暴露統一接口,支持I2C EEPROM與NOR Flash兩種后端,具備自動檢測與運行時降級能力。
設計
- 上下文:
unified_eeprom
- 策略接口:包含
read_var
,write_var
等函數指針 - 具體策略:
I2C_EEPROM_Service
、EE_NOR_Flash_Service
- 決策枚舉:
eeprom_backend_t { AUTO, I2C, FLASH, HYBRID }
// 策略接口定義
typedef struct {rt_err_t (*read_var)(int id, void *buf, size_t len);rt_err_t (*write_var)(int id, const void *buf, size_t len);
} eeprom_iface_t;// 統一上下文
typedef struct {const eeprom_iface_t *iface;eeprom_backend_t current_backend;
} unified_eeprom_t;
運行時降級
eeprom_set_backend(EEPROM_BACKEND_AUTO);rt_err_t rc = eeprom_read_var(VAR_ID_X, buf, len);
if (rc != RT_EOK) {// 失敗后自動切換策略eeprom_set_backend(EEPROM_BACKEND_FLASH);rc = eeprom_read_var(VAR_ID_X, buf, len);
}
AUTO模式下,統一層在I2C失敗時自動切換至Flash。此決策過程對上層透明,上層僅感知最終結果。
3.2 后端服務:HSM驅動的狀態模式
將I2C和Flash服務均設計為分層狀態機,封裝所有時序邏輯,實現可恢復與高可靠。
3.2.1 I2C EEPROM狀態機
- 關鍵狀態:
UNINITIALIZED
、IDLE
、READING
、WRITING
、WAITING_READY
、VERIFYING
、ERROR
- 核心邏輯:跨頁讀取、分頁寫入+輪詢就緒、可選校驗與指數退避重試
3.2.2 NOR Flash狀態機
- 關鍵狀態:
UNINITIALIZED
、VALID_PAGE
、MIGRATING
、FORMATTING
、ERROR
- 核心邏輯:雙頁交替寫、數據遷移、啟動恢復與磨損均衡
4. 協同:策略 × 狀態機
在“高可靠EEPROM抽象層”案例中,我們已經看到了策略模式與狀態機的協同:
- 策略層 (統一層):決定“走哪條路徑”,即選擇I2C后端還是Flash后端。
- 狀態機層 (后端服務):負責“路徑內部的時序與恢復”,如I2C的寫后輪詢、Flash的頁面遷移。
兩者協同時,可在復雜約束下實現透明降級與高可用。
這種“策略包含狀態機”的組合非常強大,下面我們深入探討其兩種主要協同模式。
4.1 模式一:將“完整狀態機”作為策略單元
這是EEPROM案例的核心架構模式。我們把一個“完整的狀態機(包含其所有狀態、轉移邏輯和動作)”封裝成一個策略實現。Context僅需持有指向不同狀態機策略的指針,即可在運行時“一鍵切換整體行為”。
這相當于將I2C_EEPROM_Service
和EE_NOR_Flash_Service
這兩個復雜的狀態機,包裝成符合eeprom_iface_t
接口的策略單元。
適用場景:
- 復雜流程的替換:將“I2C讀寫流程”與“Flash讀寫流程”抽象為可互換的策略。
- 行為隔離:兩套狀態機邏輯完全獨立,互不干擾,便于獨立開發與測試。
4.2 模式二:在“狀態內部”嵌入子策略
當狀態機的主流程固定,但某個具體狀態的“動作”或“算法”需要靈活替換時,可以在該狀態內部嵌入一個“子策略”。
例如,在Flash_EEPROM_Service
的MIGRATING
狀態中,數據校驗可以是一個子策略。我們可以根據系統要求,在“快速校驗(CRC8)”和“高可靠校驗(CRC32)”之間切換,而無需改變狀態機的主體結構。
// MIGRATING狀態的動作函數
void on_migrating_entry(hsm_t *sm, const hsm_event_t *event) {// ...// 根據運行時配置選擇校驗策略if (is_fast_mode()) {ctx->crc_strategy = &crc8_strategy;} else {ctx->crc_strategy = &crc32_strategy;}// ...
}// 在遷移過程中使用子策略
void perform_migration_step(context_t *ctx) {// ...uint32_t crc = ctx->crc_strategy->calculate(data, len);// ...
}
適用場景:
- 算法替換:狀態機結構穩定,但內部算法需要動態調整(如加密、壓縮、校驗算法)。
- 功能開關:在不改變狀態流程的情況下,通過空策略(noop)實現功能的動態開啟或關閉。
4.3 C代碼示例:用策略管理多狀態機
下面的代碼演示了“模式一”的通用實現,即如何通過策略模式來管理和切換兩個完全獨立的狀態機流程。
// "狀態機策略"的統一接口
typedef struct sm_ctx_s sm_ctx_t;
typedef struct {void (*init)(sm_ctx_t*);void (*dispatch)(sm_ctx_t*, int event);
} sm_strategy_t;// 上下文,持有當前的狀態機策略
struct sm_ctx_s {const sm_strategy_t *ops;void *state_data; // 指向具體狀態機的私有數據
};// --- 狀態機A (例如: I2C EEPROM) ---
void sma_init(sm_ctx_t *c){ /* ... 狀態機A初始化 ... */ }
void sma_dispatch(sm_ctx_t *c, int e){ /* ... A的事件處理 ... */ }
const sm_strategy_t smA_strategy = { sma_init, sma_dispatch };// --- 狀態機B (例如: Flash EEPROM) ---
void smb_init(sm_ctx_t *c){ /* ... 狀態機B初始化 ... */ }
void smb_dispatch(sm_ctx_t *c, int e){ /* ... B的事件處理 ... */ }
const sm_strategy_t smB_strategy = { smb_init, smb_dispatch };// 應用層通過策略接口與狀態機交互
void application_logic(void) {sm_ctx_t fsm;// 根據條件選擇一個完整的狀態機流程作為當前策略if (use_i2c_backend()) {fsm.ops = &smA_strategy;} else {fsm.ops = &smB_strategy;}// 后續操作對具體狀態機無感知fsm.ops->init(&fsm);while (has_event()) {fsm.ops->dispatch(&fsm, next_event());}
}
實踐要點(結合統一EEPROM案例):
- 策略層:
unified_eeprom
負責選擇 I2C 或 Flash 后端(含 AUTO/降級),對上層透明。 - 狀態機層:
i2c_eeprom_service
和ee_nor_flash_service
各自封裝頁寫、遷移、恢復等復雜時序與錯誤處理。 - 并發與鎖序:策略層持短鎖做選擇,狀態機在各自內部串行化操作,避免跨層嵌套鎖。
- 失敗處理:策略層負責統計健康度并決策是否切換;狀態機負責局部的、可恢復的重試。
5. 小結
- 在嵌入式C中,可通過“上下文 + 接口”框架復用策略與狀態模式實現結構。
- 策略模式關注“如何做”,決策權在外部;狀態模式關注“何時做、做什么”,決策權在內部。
- 將策略與狀態機結合,可構建透明、可降級、高可用的分層架構。