1. 策略模式的核心概念解析
策略模式(Strategy Pattern)是面向对象设计中最经典的行为型模式之一,它定义了算法家族并分别封装起来,使它们可以相互替换。这种模式让算法的变化独立于使用算法的客户端,完美体现了"开闭原则"。
在C语言这种非面向对象的语言中实现策略模式,需要一些特殊的技巧。与C++/Java等语言通过虚函数表实现多态不同,C语言通常采用函数指针+结构体的方式模拟面向对象特性。这种实现方式虽然原始,但在嵌入式开发、操作系统内核等对性能要求苛刻的场景中非常常见。
我曾在多个嵌入式项目中采用策略模式重构过复杂的业务逻辑。最典型的案例是一个工业控制器的通信协议栈,需要支持Modbus、CANopen、Profinet三种协议的自由切换。通过策略模式实现后,新增协议类型时完全不需要修改核心处理逻辑,只需要增加新的策略实现即可。
2. C语言实现策略模式的技术路线
2.1 基础架构设计
在C语言中,策略模式的标准实现通常包含以下组件:
c复制// 策略接口声明(函数指针类型)
typedef void (*StrategyFunc)(void *context);
// 策略上下文结构体
struct StrategyContext {
StrategyFunc strategy;
// 其他上下文数据...
};
// 具体策略实现A
void StrategyA_Execute(void *context) {
struct StrategyContext *ctx = (struct StrategyContext *)context;
// 实现具体算法...
}
// 具体策略实现B
void StrategyB_Execute(void *context) {
// 不同的算法实现...
}
这种实现方式的关键在于:
- 使用typedef定义统一的策略函数签名
- 通过结构体封装策略指针和上下文数据
- 每种具体策略都是符合签名的独立函数
2.2 动态策略切换机制
在实际项目中,策略往往需要运行时动态切换。我推荐以下实现方式:
c复制void SetStrategy(struct StrategyContext *ctx, StrategyFunc newStrategy) {
// 可在此添加策略切换前的清理工作
ctx->strategy = newStrategy;
// 可添加策略切换后的初始化工作
}
// 使用示例
struct StrategyContext ctx;
SetStrategy(&ctx, StrategyA_Execute);
ctx.strategy(&ctx); // 执行策略A
// 运行时切换
SetStrategy(&ctx, StrategyB_Execute);
ctx.strategy(&ctx); // 现在执行策略B
重要提示:在多线程环境下切换策略时,必须考虑线程安全问题。我通常采用互斥锁保护策略指针的读写操作。
3. 实战案例:排序算法策略实现
让我们通过一个完整的排序算法示例来演示策略模式的实际应用。这个案例来自我参与优化的一个嵌入式数据处理系统。
3.1 接口定义与上下文
c复制// 排序策略函数指针类型
typedef void (*SortStrategy)(int *array, size_t size);
// 排序上下文结构体
typedef struct {
SortStrategy strategy;
int *data_array;
size_t array_size;
// 可以添加性能统计等扩展字段
} SortContext;
3.2 具体策略实现
c复制// 冒泡排序策略
void BubbleSortStrategy(int *array, size_t size) {
for(size_t i=0; i<size-1; i++) {
for(size_t j=0; j<size-i-1; j++) {
if(array[j] > array[j+1]) {
int temp = array[j];
array[j] = array[j+1];
array[j+1] = temp;
}
}
}
}
// 快速排序策略
void QuickSortStrategy(int *array, size_t size) {
// 实现快速排序算法
// ...
}
// 选择排序策略
void SelectionSortStrategy(int *array, size_t size) {
// 实现选择排序算法
// ...
}
3.3 客户端使用示例
c复制void ProcessData(SortContext *ctx) {
printf("Before sorting:\n");
PrintArray(ctx->data_array, ctx->array_size);
// 执行当前设置的排序策略
ctx->strategy(ctx->data_array, ctx->array_size);
printf("After sorting:\n");
PrintArray(ctx->data_array, ctx->array_size);
}
int main() {
int data[] = {5,2,9,1,5,6};
size_t data_size = sizeof(data)/sizeof(data[0]);
SortContext ctx = {
.strategy = BubbleSortStrategy,
.data_array = data,
.array_size = data_size
};
ProcessData(&ctx);
// 切换策略不需要修改ProcessData函数
ctx.strategy = QuickSortStrategy;
ProcessData(&ctx);
return 0;
}
4. 高级应用技巧与优化
4.1 策略组合模式
在实际项目中,我经常遇到需要组合多个策略的情况。例如在一个网络协议处理系统中,可能需要组合数据解析策略+加密策略+压缩策略。
c复制typedef struct {
ParseStrategy parser;
EncryptStrategy encryptor;
CompressStrategy compressor;
// 其他组合策略...
} CompositeStrategy;
void ProcessPacket(CompositeStrategy *strategy, Packet *packet) {
Packet *parsed = strategy->parser(packet);
Packet *encrypted = strategy->encryptor(parsed);
Packet *compressed = strategy->compressor(encrypted);
// 后续处理...
}
4.2 策略工厂模式
当策略对象创建逻辑复杂时,可以引入工厂模式:
c复制typedef enum {
SORT_BUBBLE,
SORT_QUICK,
SORT_SELECTION
} SortAlgorithmType;
SortStrategy CreateSortStrategy(SortAlgorithmType type) {
switch(type) {
case SORT_BUBBLE: return BubbleSortStrategy;
case SORT_QUICK: return QuickSortStrategy;
case SORT_SELECTION: return SelectionSortStrategy;
default: return NULL;
}
}
4.3 性能优化技巧
-
函数指针缓存:在频繁调用的场景中,可以将策略函数指针缓存到局部变量:
c复制void ProcessData(SortContext *ctx) { SortStrategy local_strategy = ctx->strategy; // 缓存 local_strategy(ctx->data_array, ctx->array_size); } -
策略预加载:对于初始化耗时的策略,可以实现预加载机制:
c复制void PreloadStrategies() { // 提前初始化所有策略需要的资源 InitBubbleSort(); InitQuickSort(); }
5. 常见问题与解决方案
5.1 策略接口不一致
问题现象:不同策略需要的参数不一致,导致难以统一接口。
解决方案:
- 使用上下文结构体封装所有可能需要的参数
- 采用适配器模式包装不同接口的策略
c复制// 适配器示例
typedef struct {
int *array;
size_t size;
// 其他可能需要的参数...
} SortParams;
void AdaptedQuickSort(void *params) {
SortParams *p = (SortParams *)params;
// 调用原始快速排序
OriginalQuickSort(p->array, p->size);
}
5.2 策略状态管理
问题现象:某些策略需要维护内部状态(如缓存、计数器等)。
解决方案:
- 在上下文结构中增加状态字段
- 使用策略初始化/清理函数
c复制typedef struct {
StrategyFunc strategy;
void *strategy_state; // 策略私有状态
// 其他上下文...
} StrategyContext;
void InitStrategy(StrategyContext *ctx) {
if(ctx->strategy == CacheStrategy) {
ctx->strategy_state = CreateCache(1024);
}
}
void CleanupStrategy(StrategyContext *ctx) {
if(ctx->strategy_state) {
FreeCache(ctx->strategy_state);
}
}
5.3 多线程安全问题
问题场景:在多线程环境中,策略指针可能被同时读写。
解决方案:
- 使用互斥锁保护策略指针
- 采用原子操作更新策略指针
c复制#include <pthread.h>
typedef struct {
StrategyFunc strategy;
pthread_mutex_t lock;
// ...
} ThreadSafeContext;
void SetStrategySafe(ThreadSafeContext *ctx, StrategyFunc newStrategy) {
pthread_mutex_lock(&ctx->lock);
ctx->strategy = newStrategy;
pthread_mutex_unlock(&ctx->lock);
}
6. 实际项目经验分享
在嵌入式网络设备开发中,我们使用策略模式实现了灵活的数据包处理流水线。系统需要支持多种协议(IPv4/IPv6)和多种加密方式(AES/DES/3DES)。
最初版本使用条件语句判断协议类型:
c复制void ProcessPacket(Packet *pkt) {
if(pkt->version == IPV4) {
ProcessIPv4(pkt);
} else if(pkt->version == IPV6) {
ProcessIPv6(pkt);
}
// ...
}
重构为策略模式后:
c复制typedef struct {
ParseStrategy parser;
EncryptStrategy encryptor;
// 其他处理策略...
} PacketProcessor;
void ProcessPacket(PacketProcessor *proc, Packet *pkt) {
Packet *parsed = proc->parser(pkt);
Packet *encrypted = proc->encryptor(parsed);
// ...
}
这种改造带来了三个显著优势:
- 新增协议类型时只需添加新策略,不修改核心逻辑
- 可以运行时动态切换处理策略
- 不同策略可以独立测试和优化
在性能优化阶段,我们发现函数指针调用会有约5%的性能开销。通过以下技巧将开销降到了1%以内:
- 在关键循环外部缓存函数指针
- 使用GCC的__attribute__((always_inline))提示内联小型策略
- 对高频调用的策略使用静态策略设置(避免动态切换)