第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次间接跳转 + vtable1次间接跳转解释执行或元数据查找,开销大
策略数量扩展无限制,易于管理需维护函数指针数组灵活但性能不可控
运行时切换粒度对象级别,安全需替换指针,注意完整性字符串驱动,可热更新
推荐场景标准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. 步骤1:提取算法共性——从业务场景中识别出可替换的算法变体,抽象输入/输出结构。
  2. 步骤2:定义策略接口——在Framework公共层声明纯虚类或函数签名,确保所有策略实现保持同一调用约定。
  3. 步骤3:实现各策略——每个策略类单独文件,无业务依赖,通过单元测试验证。
  4. 步骤4:构建上下文——持有一个策略引用(或原子指针),提供构造注入和setter方法。
  5. 步骤5:建立切换机制——通过配置、路由表或动态加载库,触发 setStrategy。

4.8 验收标准

  • 上下文的输出仅由当前策略决定,同一数据在不同策略下产生不同结果。
  • 运行时切换后,后续请求立即使用新策略,旧策略实例可安全释放(无引用泄漏)。
  • 在高频率切换测试(每秒1000次切换 + 10000次调用)中,吞吐量下降不超过5%(与固定策略相比)。
  • 所有策略实现可通过配置文件或注册表发现,无需修改上下文代码即可扩展。
策略模式架构及运行时替换 上下文 (Context) IStrategy ConcreteStrategyA ConcreteStrategyB ConcreteStrategyC 运行时切换域(通过 setStrategy 替换引用) ↻ 运行时替换:外部组件调用 context.setStrategy(newStrategy),上下文原子更新指针,后续委托自动切换。