第4章 策略模式处理业务算法动态切换:策略接口、上下文装配、运行时替换、性能考量
4.1 边界与分层
策略模式在Framework层中的边界由三个逻辑层构成:策略契约层、策略实现层与上下文装配层。契约层定义算法可调用的统一接口,不含任何业务状态;实现层包含所有可替换算法变体,各自封装于独立类中,不依赖外部上下文;装配层负责将具体策略注入上下文,并对调用方屏蔽策略选择细节。
| 分层 | 边界职责 | 跨层通信 |
|---|---|---|
| 策略契约层 | 定义纯虚接口或函数指针类型 | 仅传递参数与返回值 |
| 策略实现层 | 各算法独立实现,可热替换 | 实现类之间不允许直接依赖 |
| 上下文装配层 | 持有策略引用,提供切换接口 | 通过依赖注入完成装配,不感知内部实现 |
4.2 组件职责
- 策略接口(IStrategy):声明唯一执行方法,例如
execute(InputData) -> OutputData。职责是约定输入输出结构,不包含任何业务逻辑。 - 具体策略(ConcreteStrategyA/B/C):实现接口,封装特定算法。每个策略应保持无状态或只包含线程安全的状态,便于运行时替换。
- 上下文(Context):持有策略接口的引用,提供
setStrategy(IStrategy*)方法实现运行时装配。上下文自身不关心具体算法执行流程,仅委托。
架构提示:策略接口应设计为纯虚类(或函数签名),避免将业务参数绑定在接口中。参数交给上下文在调用时传入,解耦更彻底。
4.3 数据流与控制流
数据流:外部请求到达上下文 → 上下文提取参数 → 调用当前策略的 execute → 策略返回计算结果 → 上下文封装响应返回。
控制流:运行时通过条件判断或配置驱动,调用上下文的 setStrategy 切换策略指针。切换可在请求间(线程安全)或请求内(需谨慎同步)发生。
控制流关键点:策略切换的触发点应集中管理(如配置中心回调、策略工厂),避免散落在业务代码中。
4.4 信任链路
策略替换本质是“上下文将执行权委托给外部注入的算法”。信任链路要求:
- 策略实例必须由上层模块(如策略工厂、IoC容器)创建并注入,上下文不自行实例化。
- 所有策略必须通过静态类型检查(接口约束)和单元测试验证。
- 运行时替换需保证线程安全:若上下文可能被多个线程同时调用,应使用原子指针或读写锁保护策略引用。
4.5 选型权衡
| 维度 | 虚函数(C++/Java) | 函数指针(C/Framework C) | 动态解析(反射/脚本) |
|---|---|---|---|
| 单次调用开销 | 1~2次间接跳转 + vtable | 1次间接跳转 | 解释执行或元数据查找,开销大 |
| 策略数量扩展 | 无限制,易于管理 | 需维护函数指针数组 | 灵活但性能不可控 |
| 运行时切换粒度 | 对象级别,安全 | 需替换指针,注意完整性 | 字符串驱动,可热更新 |
| 推荐场景 | 标准Framework层 | 高性能嵌入式或C接口 | 配置化、脚本化快速迭代 |
性能陷阱:运行时切换若发生在高频循环内,应尽量将策略选择提前到循环外,或使用分支预测友好的策略数组+索引方式,避免每次循环都执行一次虚函数调用。
4.6 风险点
- 策略膨胀:策略类数量随算法变体线性增长,需建立策略注册表或工厂模式统一管理。
- 上下文耦合:若上下文暴露过多状态给策略,会破坏解耦。策略应通过参数对象获取所需数据,不可直接访问上下文私有成员。
- 线程安全:多线程环境下替换策略指针可能造成另一线程读到的不一致状态。建议使用
std::atomic或不可变策略对象。
以下是一段基于C++11的上下文装配示例,展示接口定义与运行时替换的基本模式:
// 策略接口
class ICompression {
public:
virtual ~ICompression() = default;
virtual void compress(const std::string& data, std::ostream& out) = 0;
};
// 具体策略:ZIP
class ZipCompression : public ICompression {
public:
void compress(const std::string& data, std::ostream& out) override {
// 实现ZIP算法
}
};
// 具体策略:GZIP
class GzipCompression : public ICompression {
public:
void compress(const std::string& data, std::ostream& out) override {
// 实现GZIP算法
}
};
// 上下文
class CompressionContext {
std::atomic<ICompression*> strategy_;
public:
explicit CompressionContext(ICompression* initial) : strategy_(initial) {}
void setStrategy(ICompression* s) { strategy_.store(s, std::memory_order_release); }
void compress(const std::string& data, std::ostream& out) {
auto* s = strategy_.load(std::memory_order_acquire);
s->compress(data, out); // 委托
}
};
4.7 落地步骤
- 步骤1:提取算法共性——从业务场景中识别出可替换的算法变体,抽象输入/输出结构。
- 步骤2:定义策略接口——在Framework公共层声明纯虚类或函数签名,确保所有策略实现保持同一调用约定。
- 步骤3:实现各策略——每个策略类单独文件,无业务依赖,通过单元测试验证。
- 步骤4:构建上下文——持有一个策略引用(或原子指针),提供构造注入和setter方法。
- 步骤5:建立切换机制——通过配置、路由表或动态加载库,触发
setStrategy。
4.8 验收标准
- 上下文的输出仅由当前策略决定,同一数据在不同策略下产生不同结果。
- 运行时切换后,后续请求立即使用新策略,旧策略实例可安全释放(无引用泄漏)。
- 在高频率切换测试(每秒1000次切换 + 10000次调用)中,吞吐量下降不超过5%(与固定策略相比)。
- 所有策略实现可通过配置文件或注册表发现,无需修改上下文代码即可扩展。