策略模式

策略模式是什么

策略模式(Strategy Pattern)是一种 行为型 设计模式,核心思想是:

把“一组可互相替换的算法”封装成独立类,让它们实现同一个接口,运行时按需切换,而不用修改客户端代码。


一、生活例子 10 秒懂

  1. 去机场有 3 种算法:地铁、大巴、打车。
  2. 算法各自封装成类(MetroStrategy、BusStrategy、TaxiStrategy)。
  3. 你手里只拿一张“去机场”的通用票(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++ 味儿」的策略模式速查卡

  1. 把算法抽象成 Strategy 接口(纯虚类);
  2. 每个具体算法是一个 ConcreteStrategy 类;
  3. 客户代码只依赖接口,运行时通过 组合 / 依赖注入 切换算法;
  4. 如果算法无状态,直接用 静态对象 + 引用 可避免反复 new;
  5. 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>

// 1. 策略接口
class PayStrategy {
public:
virtual ~PayStrategy() = default;
virtual void pay(int amount) const = 0;
};

// 2. 具体算法
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';
}
};

// 3. 上下文(可更换策略)
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);
}
};

// 4. 使用
int main() {
Order order(std::make_unique<WechatPay>());
order.checkout(100);
order.set_strategy(std::make_unique<CardPay>());
order.checkout(200);
}

输出:

1
2
[微信支付] ¥100
[银行卡] ¥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++运行时多态的特性,先声明一个纯虚类,让子类实现父类的纯虚函数,接着在业务逻辑代码中使用父类的指针或者引用。当子类在程序中构造,使用业务代码时,父类指针会指向子类对象的虚函数表,会根据业务中所调用的函数,来找到子类实现的函数进行调用。

​ 其实就是接口作用,调用相同的接口,传入不同的数据就会产生不同的效果。

个人总结补充

你的总结抓住了“运行时多态”这一常见实现路径,但还不够完整,也容易让人误以为“策略模式 = 运行时多态”。下面给你三点反馈,帮你把边界补齐:


  1. ✅ 对的部分
  • 在 C++ 里,经典策略模式确实就是:
    纯虚类(接口) → 子类 override → 业务代码持有父类指针/引用 → 运行期通过虚函数表分派。
  • 这种写法完全符合“接口相同,传入不同子类 → 行为不同”的直觉。

  1. ⚠️ 缺的部分
  • 设计意图 ≠ 实现技巧
    策略模式的核心意图是“把算法族封装、可互换、客户端不依赖具体算法”。
    只要满足这个意图,不用虚函数、不用类、甚至不用运行时多态也能叫策略模式(见下面 3)。

  • 编译期策略(零虚表)

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); }

连“类”都没有,照样实现策略模式。


  1. 一句话校正
  • 运行时多态(虚函数表)只是 C++ 实现策略模式的“常用手段”之一,不是策略模式的定义。
  • 策略模式 = “算法可插拔”的设计思想;至于用虚函数、模板、函数指针还是 lambda,都只是不同语言、不同性能需求下的实现选择

所以你的总结可以改成:

“在 C++ 里,经典策略模式通常利用运行时多态(纯虚类 + 虚函数表)来实现算法切换,但策略模式本身并不局限于运行时多态,编译期多态甚至函数指针同样可以达成‘算法可互换’的意图。”