策略模式:算法族封装,运行时切换
策略模式,说白了就是「把算法打包成独立的策略,让它们可以互相替换」。我最早接触这个模式,是在做一个支付系统的时候——那时候要对接支付宝、微信、银联,每种支付方式的签名算法、请求格式都不一样。如果全写在 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(); // 输出:使用冒泡排序
策略模式的结构图
下面这张图,把策略模式的整体结构画清楚了。我习惯用这种图来跟团队沟通设计思路。
什么时候用策略模式?
我个人习惯,遇到以下场景就会考虑策略模式:
- 多个类只有行为不同——比如多种排序、多种压缩、多种加密
- 需要避免大量条件分支——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