適配器模式將**不兼容的接口**轉換為**客戶期望的接口**,使原本無法協作的類能夠協同工作。雖然 C 語言無類/繼承,但通過**結構體 + 函數指針**可完美實現對象適配器模式。本文給出標準 C 實現、C# 對比參考、適用場景判斷、重構優先原則。
+----------------+ +----------------+ +----------------+
| Client | ---> | Target | ---> | Adaptee |
| (期望 Target) | 介面 | (抽象目標介面) | 委託 | (被適配者/舊系統) |
+----------------+ +----------------+ +----------------+
▲
│ 繼承/實現
│
+----------------+
| Adapter |
| (持有 Adaptee) |
+----------------+
// target.h
typedef struct Target Target;
struct Target {
void (*request)(Target *self); // 虛函數指針
};
// 默認實現
void target_default_request(Target *self) {
printf("普通請求!\n");
}
Target *target_create() {
Target *t = malloc(sizeof(Target));
t->request = target_default_request;
return t;
}
// adaptee.h
typedef struct Adaptee Adaptee;
struct Adaptee {
// 無 request(),只有特殊介面
};
void adaptee_specific_request(Adaptee *self) {
printf("特殊請求!\n");
}
Adaptee *adaptee_create() {
return calloc(1, sizeof(Adaptee));
}
// adapter.h
#include "target.h"
#include "adaptee.h"
typedef struct Adapter {
Target target; // 嵌入 Target (繼承模擬:前綴相同即可向上轉型)
Adaptee *adaptee; // 持有被適配者 (組合)
} Adapter;
// 適配器的 request 實現:委託給 adaptee
void adapter_request(Target *self) {
Adapter *adapter = (Adapter *)self; // 向下轉型
adaptee_specific_request(adapter->adaptee);
}
Target *adapter_create() {
Adapter *a = malloc(sizeof(Adapter));
a->target.request = adapter_request; // 覆寫虛函數
a->adaptee = adaptee_create();
return (Target *)a; // 向上轉型返回 Target*
}
void adapter_destroy(Target *self) {
Adapter *a = (Adapter *)self;
free(a->adaptee);
free(a);
}
int main() {
Target *t = adapter_create(); // 客戶端只認 Target*
t->request(t); // 輸出: 特殊請求!
adapter_destroy(t);
return 0;
}
// Target
class Target {
public virtual void Request() => Console.WriteLine("普通請求!");
}
// Adaptee
class Adaptee {
public void SpecificRequest() => Console.WriteLine("特殊請求!");
}
// Adapter (對象適配器)
class Adapter : Target {
private Adaptee _adaptee = new Adaptee();
public override void Request() => _adaptee.SpecificRequest();
}
// Client
Target target = new Adapter();
target.Request(); // 特殊請求!
| 場景特徵 | 是否適用適配器 | 替代方案優先級 |
|---|---|---|
| 第三方庫/舊代碼接口與當前系統不兼容,且**無法修改源碼** | ✅ **首選** | 無 |
| 需要復用現有類,但接口不匹配新系統需求 | ✅ 適用 | 重構統一接口 (若可控) |
| 設計階段預見接口不一致 | ❌ **不適用** | **統一接口設計** (介面隔離、依賴倒置) |
| 可修改源碼,且接口差異小 | ❌ 過度設計 | **直接重構/修改接口** |
| 需要適配多個不同來源的類到同一目標接口 | ✅ 適用 | 統一門面模式 |
Target,不依賴具體 Adaptee。Adaptee 即可納入新體系。Adapter,不污染業務代碼。Adaptee 內部狀態/行為語義與 Target 根本衝突,適配器無能為力。struct Adapter { Target target; ...; },Target* 與 Adapter* 內存佈局兼容 (首地址相同),直接強轉實現向上/向下轉型。target.request = adapter_request; 動態綁定。create/destroy 成對函數,構造時初始化 vtable、創建被適配者,析構時逆序釋放。struct Target;,隱藏實現細節,降低編譯依賴。設計階段統一接口 > 重構修正接口 > 適配器彌補接口
只有面對**不可修改的第三方庫、歷史遺留系統、硬件驅動接口**等「硬約束」時,才應引入適配器。在可控代碼中,**介面隔離原則 (ISP) + 依賴倒置原則 (DIP) + 統一抽象層** 才是從根源消除接口不兼容的正道。C 語言通過「結構體嵌入 + 函數指針 + 組合」零開銷實現對象適配器,是系統級開發中接口治理的利器。