策略模式
策略模式是什么
策略模式(Strategy Pattern)是一种 行为型 设计模式,核心思想是:
把“一组可互相替换的算法”封装成独立类,让它们实现同一个接口,运行时按需切换,而不用修改客户端代码。
一、生活例子 10 秒懂
- 去机场有 3 种算法:地铁、大巴、打车。
- 算法各自封装成类(MetroStrategy、BusStrategy、TaxiStrategy)。
- 你手里只拿一张“去机场”的通用票(Context),想省钱就插地铁算法,想快就插打车算法——切换即插即用。
二、代码骨架(语言无关)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| ┌----------------┐ │ Strategy接口 │◀----┐ │ +algorithm() │ │ └----------------┘ │ 实现 △ │ ┌-----------------┼---------------┼---------------┐ │ │ │ │ ┌-------------┐ ┌-------------┐ ┌-------------┐ ┌-------------┐ │ConcreteStrA │ │ConcreteStrB │ │ConcreteStrC │ │ConcreteStrD │ └-------------┘ └-------------┘ └-------------┘ └-------------┘ △ △ △ △ │ │ │ │ └-------------┼-------------┼-------------┘ │ │ ┌-----▼-------------▼-----┐ │ Context │ │ -strategy: Strategy │ │ +setStrategy() │ │ +executeAlgorithm() ----┼----▶ 实际调用 strategy.algorithm() └-------------------------┘
|
三、与其他模式区别
| 模式 |
关注点 |
| 策略模式 |
多算法 互相替换 |
| 模板方法 |
算法 骨架固定,子类只填某些步骤 |
| 状态模式 |
状态 自动流转,行为随状态而变 |
一句话记忆
“把‘怎么做’抽象成一族可插拔的算法,运行时想换就换,代码里再也见不到 long if-else。”
下面给出一份「C++ 味儿」的策略模式速查卡
- 把算法抽象成 Strategy 接口(纯虚类);
- 每个具体算法是一个 ConcreteStrategy 类;
- 客户代码只依赖接口,运行时通过 组合 / 依赖注入 切换算法;
- 如果算法无状态,直接用 静态对象 + 引用 可避免反复 new;
- C++20 以后,还可以用 lambda + std::function 或 模板策略把运行时多态变成编译期多态,性能再涨一档。
一、经典面向对象版(运行期多态)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
| #include <iostream> #include <memory>
class PayStrategy { public: virtual ~PayStrategy() = default; virtual void pay(int amount) const = 0; };
class WechatPay : public PayStrategy { public: void pay(int amount) const override { std::cout << "[微信支付] ¥" << amount << '\n'; } };
class CardPay : public PayStrategy { public: void pay(int amount) const override { std::cout << "[银行卡] ¥" << amount << '\n'; } };
class Order { std::unique_ptr<PayStrategy> strategy_; public: explicit Order(std::unique_ptr<PayStrategy> s) : strategy_(std::move(s)) {} void checkout(int amount) const { strategy_->pay(amount); } void set_strategy(std::unique_ptr<PayStrategy> s) { strategy_ = std::move(s); } };
int main() { Order order(std::make_unique<WechatPay>()); order.checkout(100); order.set_strategy(std::make_unique<CardPay>()); order.checkout(200); }
|
输出:
二、无状态策略 → 单例 + 引用(省 new)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| struct WechatPay { void pay(int amount) const { std::cout << "[微信支付] ¥" << amount << '\n'; } static WechatPay& inst() { static WechatPay s; return s; } };
struct CardPay { void pay(int amount) const { std::cout << "[银行卡] ¥" << amount << '\n'; } static CardPay& inst() { static CardPay s; return s; } };
template <typename Strategy> class OrderT { Strategy& strategy_; public: explicit OrderT(Strategy& s) : strategy_(s) {} void checkout(int amount) const { strategy_.pay(amount); } };
int main() { OrderT wechat(WechatPay::inst()); wechat.checkout(100); OrderT card(CardPay::inst()); card.checkout(200); }
|
三、现代 C++20:lambda 策略(编译期多态)
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| #include <iostream> #include <functional>
using PayStrategy = std::function<void(int)>;
inline void checkout(int amount, PayStrategy s) { s(amount); }
int main() { auto wechat = [](int a){ std::cout << "[微信支付] ¥" << a << '\n'; }; auto card = [](int a){ std::cout << "[银行卡] ¥" << a << '\n'; };
checkout(100, wechat); checkout(200, card); }
|
四、何时用策略模式
| 场景 |
示例 |
| 支付/物流计价 |
微信、支付宝、银联、组合支付 |
| 压缩/序列化 |
zip、bz2、zstd、json、protobuf |
| 排序/搜索 |
快排、归并、桶排 |
| AI 决策 |
贪婪、蒙特卡洛、Minimax |
| 路线规划 |
地铁、公交、步行、打车 |
五、优缺点速记
优点
- 干掉 long switch / if-else
- 符合开闭原则:新增算法只加类
- 运行时热插拔
缺点
- 类数量膨胀(可结合 lambda / template 缓解)
- 客户端必须知道有哪些策略(可用简单工厂封装)
一句话总结
策略模式 = 把“算法族”抽象成接口,客户代码只依赖接口,想换算法就换对象,C++ 里用虚函数、lambda、模板都能玩,核心就是“面向接口编程 + 组合替代继承”。
个人总结
其实策略模式就是使用了C++运行时多态的特性,先声明一个纯虚类,让子类实现父类的纯虚函数,接着在业务逻辑代码中使用父类的指针或者引用。当子类在程序中构造,使用业务代码时,父类指针会指向子类对象的虚函数表,会根据业务中所调用的函数,来找到子类实现的函数进行调用。
其实就是接口作用,调用相同的接口,传入不同的数据就会产生不同的效果。
个人总结补充
你的总结抓住了“运行时多态”这一常见实现路径,但还不够完整,也容易让人误以为“策略模式 = 运行时多态”。下面给你三点反馈,帮你把边界补齐:
- ✅ 对的部分
- 在 C++ 里,经典策略模式确实就是:
纯虚类(接口) → 子类 override → 业务代码持有父类指针/引用 → 运行期通过虚函数表分派。
- 这种写法完全符合“接口相同,传入不同子类 → 行为不同”的直觉。
- ⚠️ 缺的部分
1 2 3 4 5 6
| template <class Payment> class Order { Payment pay_; public: void checkout(int amt) { pay_(amt); } };
|
这里没有父类、没有指针、没有虚函数表,但设计意图仍是策略模式,只是编译期就绑定了算法。
- 函数指针 / lambda 策略(C 语言也能写)
1 2
| typedef void (*PayFunc)(int); void checkout(int amt, PayFunc pf) { pf(amt); }
|
连“类”都没有,照样实现策略模式。
- 一句话校正
- 运行时多态(虚函数表)只是 C++ 实现策略模式的“常用手段”之一,不是策略模式的定义。
- 策略模式 = “算法可插拔”的设计思想;至于用虚函数、模板、函数指针还是 lambda,都只是不同语言、不同性能需求下的实现选择。
所以你的总结可以改成:
“在 C++ 里,经典策略模式通常利用运行时多态(纯虚类 + 虚函数表)来实现算法切换,但策略模式本身并不局限于运行时多态,编译期多态甚至函数指针同样可以达成‘算法可互换’的意图。”