策略模式:算法族封装,运行时切换

策略模式,说白了就是「把算法打包成独立的策略,让它们可以互相替换」。我最早接触这个模式,是在做一个支付系统的时候——那时候要对接支付宝、微信、银联,每种支付方式的签名算法、请求格式都不一样。如果全写在 if-else 里,那代码简直没法看。

嗯,策略模式就是来解决这个问题的。

核心思想:把变化的部分抽出来

策略模式的核心,就三句话:

  • 定义一组算法(策略接口)
  • 每个算法独立封装(具体策略类)
  • 运行时自由切换(上下文持有策略引用)

你想想看,排序算法就是个绝佳的例子。冒泡、快排、归并,它们做的事情一样——排序,但实现方式完全不同。策略模式允许你在运行时根据数据量、数据特征,动态选择最合适的排序策略。

关键原则:「开闭原则」——对扩展开放,对修改关闭。加一个新策略,不需要改现有代码。

C++ 中的排序策略选择

我直接上代码吧。假设我们有一个数据处理器,需要支持多种排序方式。

// 策略接口
class SortStrategy {
public:
    virtual ~SortStrategy() = default;
    virtual void sort(std::vector<int>& data) = 0;
};

// 具体策略:快速排序
class QuickSort : public SortStrategy {
public:
    void sort(std::vector<int>& data) override {
        std::cout << "使用快速排序" << std::endl;
        std::sort(data.begin(), data.end());
    }
};

// 具体策略:冒泡排序
class BubbleSort : public SortStrategy {
public:
    void sort(std::vector<int>& data) override {
        std::cout << "使用冒泡排序" << std::endl;
        for (size_t i = 0; i < data.size() - 1; ++i) {
            for (size_t j = 0; j < data.size() - i - 1; ++j) {
                if (data[j] > data[j + 1]) {
                    std::swap(data[j], data[j + 1]);
                }
            }
        }
    }
};

// 上下文:数据处理器
class DataProcessor {
private:
    std::unique_ptr<SortStrategy> strategy_;
    std::vector<int> data_;
public:
    void setStrategy(std::unique_ptr<SortStrategy> strategy) {
        strategy_ = std::move(strategy);
    }
    
    void process() {
        if (strategy_) {
            strategy_->sort(data_);
        }
    }
    
    void addData(int value) {
        data_.push_back(value);
    }
};

你看,客户端代码只需要这样用:

DataProcessor processor;
processor.addData(5);
processor.addData(3);
processor.addData(8);

// 运行时切换策略
processor.setStrategy(std::make_unique<QuickSort>());
processor.process();  // 输出:使用快速排序

processor.setStrategy(std::make_unique<BubbleSort>());
processor.process();  // 输出:使用冒泡排序

策略模式的结构图

下面这张图,把策略模式的整体结构画清楚了。我习惯用这种图来跟团队沟通设计思路。

Context(上下文) - strategy: Strategy* + setStrategy(Strategy*) + executeStrategy() Strategy(策略接口) + algorithm() ConcreteStrategyA + algorithm() ConcreteStrategyB + algorithm() ConcreteStrategyC 持有策略引用

什么时候用策略模式?

我个人习惯,遇到以下场景就会考虑策略模式:

  • 多个类只有行为不同——比如多种排序、多种压缩、多种加密
  • 需要避免大量条件分支——if-else 或 switch-case 太多,代码又臭又长
  • 算法需要独立于客户端变化——算法本身可能频繁迭代

小技巧:策略模式可以和工厂模式搭配使用。工厂负责创建策略对象,上下文只负责使用。这样客户端连策略的具体类型都不需要知道。

避坑指南

我曾经踩过一个坑:在一个图像处理项目中,我把所有滤镜算法都做成了策略。结果策略类数量爆炸,光滤镜就有三十多种。每次新增滤镜都要新建一个类,维护成本反而上去了。

后来我改用「策略 + 模板方法」的组合——公共部分提取到基类,差异部分用虚函数暴露。策略数量从三十多个降到了五个。

所以策略模式不是银弹。如果你的算法数量可能膨胀到几十上百个,建议先想想能不能用参数化配置代替。

策略模式 vs 状态模式

这两个模式结构很像,但意图完全不同。我整理了个对比表:

维度 策略模式 状态模式
核心意图 封装算法,让它们可以互相替换 封装状态,让对象在不同状态下表现不同行为
切换时机 由客户端主动选择 由状态对象自身决定
状态管理 策略之间无关联,彼此独立 状态之间有关联,知道下一个状态是什么
典型场景 排序、压缩、加密、支付 订单状态、TCP连接状态

现代 C++ 中的策略模式

嗯,这里要提一下。现代 C++ 里,策略模式不一定非要用虚函数。模板也可以实现策略,而且性能更好——没有虚函数调用的开销。

// 模板策略:编译期绑定
template<typename SortAlgo>
class DataProcessor {
    SortAlgo algo_;
    std::vector<int> data_;
public:
    void process() {
        algo_.sort(data_);
    }
};

// 使用
DataProcessor<QuickSort> processor;
processor.process();  // 编译期就确定了策略

这种叫「基于策略的设计」(Policy-Based Design),C++ 标准库的 std::allocator 就是典型例子。不过它有个缺点——策略在编译期就固定了,没法运行时切换。

所以我的建议是:

  • 运行时需要切换 → 用虚函数策略模式
  • 编译期就能确定 → 用模板策略,性能更好

总结

策略模式,说白了就是把「怎么做」和「谁来做」分开。它让算法可以独立变化,客户端可以自由组合。我在项目中用它的频率很高——支付、排序、压缩、日志格式化,几乎每个系统都能找到它的影子。

记住一点:策略模式解决的是「算法族的封装和切换」,不是「对象状态的管理」。别跟状态模式搞混了。


公众号:蓝海资料掘金营,微信deep3321