適配器模式將一個類的介面轉換為客戶期望的另一個介面,使原本介面不兼容的類能夠協同工作。在 C 語言中,透過結構體嵌入與函數指針模擬「繼承」與「多型」,實現物件導向的適配器模式。
本例模擬籃球隊系統:
Player(攻擊/防守/銷毀)Forwards、Center、Guards 均符合 Player 介面ForeignCenter 介面不同(fattack/fdefend/fdestroy)ForeignCenterAdapter 封裝 ForeignCenter,對外暴露 Player 介面typedef.h#ifndef __TYPEDEF_H__
#define __TYPEDEF_H__
#include <stdio.h>
#include <stdlib.h>
#ifdef __cplusplus
extern "C" {
#endif
typedef enum _Ret { RET_OK, RET_FAIL } Ret;
/* 防禦性程式設計巨集 */
#define return_if_fail(p) \
if (!(p)) { \
printf("%s:%d Warning: " #p " Failed\n", __func__, __LINE__); \
return; \
}
#define return_val_if_fail(p, ret) \
if (!(p)) { \
printf("%s:%d Warning: " #p " Failed\n", __func__, __LINE__); \
return (ret); \
}
#define SAFE_FREE(p) do { if (p) { free(p); (p) = NULL; } } while (0)
#ifdef __cplusplus
}
#endif
#endif
player.h#ifndef __PLAYER_H__
#define __PLAYER_H__
#include "typedef.h"
#ifdef __cplusplus
extern "C" {
#endif
typedef struct _Player Player;
struct _Player {
char *name;
void (*attack)(void *player);
void (*defend)(void *player);
void (*destroy)(void *player);
};
/* 內聯封裝函數,提供統一呼叫入口 */
static inline void player_attack(Player *thiz) {
return_if_fail(thiz != NULL);
if (thiz->attack) thiz->attack(thiz);
}
static inline void player_defend(Player *thiz) {
return_if_fail(thiz != NULL);
if (thiz->defend) thiz->defend(thiz);
}
static inline void player_destroy(Player *thiz) {
return_if_fail(thiz != NULL);
if (thiz->destroy) thiz->destroy(thiz);
}
#ifdef __cplusplus
}
#endif
#endif
centerplayer.h/.c/* centerplayer.h */
#ifndef __CENTERPLAYER_H__
#define __CENTERPLAYER_H__
#include "player.h"
#ifdef __cplusplus
extern "C" {
#endif
typedef struct _Center Center;
struct _Center { Player player; }; // 組合:嵌入 Player 作為第一個成員
Center *CenterCreate(char *name);
#ifdef __cplusplus
}
#endif
#endif
/* centerplayer.c */
#include "centerplayer.h"
static void center_attack(void *thiz) {
printf("中鋒 %s 進攻\n", ((Center *)thiz)->player.name);
}
static void center_defend(void *thiz) {
printf("中鋒 %s 防守\n", ((Center *)thiz)->player.name);
}
static void center_destroy(void *thiz) {
return_if_fail(thiz != NULL);
SAFE_FREE(thiz);
}
Center *CenterCreate(char *name) {
Center *thiz = malloc(sizeof(Center));
if (thiz) {
thiz->player.name = name;
thiz->player.attack = center_attack;
thiz->player.defend = center_defend;
thiz->player.destroy = center_destroy;
}
return thiz;
}
foreigncenterplayer.h/.cstruct _ForeignCenter {
char *name;
void (*fattack)(void *player);
void (*fdefend)(void *player);
void (*fdestroy)(void *player);
};
/* 介面命名與 Player 不同,需適配 */
foreigncenteradapter.c#include "foreigncenterplayer.h"
#include "foreigncenteradapter.h"
static void foreign_center_adapter_attack(void *thiz) {
foreign_center_attack(thiz); // 委派給被適配物件
}
static void foreign_center_adapter_defend(void *thiz) {
foreign_center_defend(thiz);
}
static void foreign_center_adapter_destroy(void *thiz) {
foreign_center_destroy(thiz);
}
ForeignCenterAdapter *ForeignCenterAdapterCreate(char *name) {
ForeignCenterAdapter *thiz = malloc(sizeof(ForeignCenterAdapter));
if (thiz) {
/* 關鍵:建立被適配物件,並將其指標作為 adapter 的 player 成員 */
ForeignCenter *fc = ForeignCenterCreate(name);
thiz->player.name = fc->name;
thiz->player.attack = foreign_center_adapter_attack;
thiz->player.defend = foreign_center_adapter_defend;
thiz->player.destroy = foreign_center_adapter_destroy;
/* 實際專案中需妥善管理 fc 的生命週期,此處簡化處理 */
}
return thiz;
}
test.c#include "player.h"
#include "centerplayer.h"
#include "foreigncenteradapter.h"
int main(void) {
Player *yao = (Player *)CenterCreate("姚明");
Player *bateer = (Player *)ForeignCenterAdapterCreate("巴蒂爾");
player_attack(yao); // 輸出:中鋒 姚明 進攻
player_defend(bateer); // 輸出:外籍中鋒 巴蒂爾 防守
player_attack(bateer); // 輸出:外籍中鋒 巴蒂爾 進攻
player_destroy(yao);
player_destroy(bateer);
return 0;
}
MakefileEXE := test
SRCS := test.c \
player.h foreigncenteradapter.h foreigncenteradapter.c \
foreigncenterplayer.h foreigncenterplayer.c \
forwardsplayer.h forwardsplayer.c \
centerplayer.h centerplayer.c \
guardsplayer.h guardsplayer.c \
typedef.h
all: $(EXE)
$(EXE): $(SRCS)
gcc -g $^ -o $(EXE)
clean:
rm -f $(EXE) *~
| 技巧 | 說明 |
|---|---|
| 結構體嵌入模擬繼承 | 將基底結構體(Player)作為衍生結構體(Center、ForeignCenterAdapter)的第一個成員,利用記憶體布局相容性實現向上轉型 |
| 函數指針實現多型 | 在基底結構體中定義函數指針成員,各衍生類在建構函數中綁定具體實作 |
| 不透明指針封裝 | 頭檔僅前向宣告 struct _Player;,隱藏實作細節,降低耦合 |
| 防禦性巨集 | return_if_fail、SAFE_FREE 統一錯誤處理與記憶體釋放風格 |
| 內聯封裝函數 | player_attack() 等內聯函數提供統一呼叫介面,隱藏 thiz->attack(thiz) 細節 |
$ make
gcc -g test.c ... -o test
$ ./test
中鋒 姚明 進攻
外籍中鋒 巴蒂爾 防守
外籍中鋒 巴蒂爾 進攻
ForeignCenter 也能當 Player 用,可在適配器中雙向綁定函數指針ForeignCenterAdapterCreate 內部持有 ForeignCenter*,並在 destroy 時一併釋放class Adapter : public Target, private Adaptee(類適配器)或組合(物件適配器),編譯器自動處理虛函數表與轉型