1. 策略模式在C++中的核心价值
策略模式(Strategy Pattern)是我在多年C++开发中最常使用的设计模式之一。它本质上定义了一系列算法族,将每个算法封装起来,并使它们可以互相替换。这种模式让算法的变化独立于使用算法的客户。
想象你正在开发一个游戏角色系统。角色有多种攻击方式:普通攻击、魔法攻击、暴击攻击。新手程序员可能会写出这样的代码:
cpp复制class Character {
public:
void attack() {
if (attackType == NORMAL) {
// 普通攻击逻辑
} else if (attackType == MAGIC) {
// 魔法攻击逻辑
} else if (attackType == CRITICAL) {
// 暴击攻击逻辑
}
}
private:
AttackType attackType;
};
这种写法存在几个明显问题:
- 违反开闭原则 - 新增攻击类型需要修改Character类
- 条件判断臃肿 - 随着攻击类型增加,if-else会越来越长
- 难以单元测试 - 各种攻击逻辑耦合在一起
策略模式通过将攻击行为抽象为接口,完美解决了这些问题。在C++中,我们可以这样实现:
cpp复制class AttackStrategy {
public:
virtual ~AttackStrategy() = default;
virtual void execute() = 0;
};
class NormalAttack : public AttackStrategy {
public:
void execute() override { /* 普通攻击实现 */ }
};
class Character {
public:
void setAttackStrategy(std::unique_ptr<AttackStrategy> strategy) {
attackStrategy = std::move(strategy);
}
void attack() {
if (attackStrategy) {
attackStrategy->execute();
}
}
private:
std::unique_ptr<AttackStrategy> attackStrategy;
};
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. C++策略模式的典型实现方式
2.1 基于虚函数的经典实现
这是最传统的策略模式实现方式,也是大多数C++教材中介绍的方法。我们通过抽象基类定义策略接口,具体策略类继承并实现该接口。
cpp复制// 策略接口
class SortingStrategy {
public:
virtual ~SortingStrategy() = default;
virtual void sort(std::vector<int>& data) = 0;
};
// 具体策略
class QuickSort : public SortingStrategy {
public:
void sort(std::vector<int>& data) override {
// 快速排序实现
std::cout << "Sorting using QuickSort" << std::endl;
}
};
class MergeSort : public SortingStrategy {
public:
void sort(std::vector<int>& data) override {
// 归并排序实现
std::cout << "Sorting using MergeSort" << std::endl;
}
};
// 上下文类
class Sorter {
public:
void setStrategy(std::unique_ptr<SortingStrategy> strategy) {
strategy_ = std::move(strategy);
}
void executeSort(std::vector<int>& data) {
if (strategy_) {
strategy_->sort(data);
}
}
private:
std::unique_ptr<SortingStrategy> strategy_;
};
使用示例:
cpp复制std::vector<int> data = {5, 2, 8, 1, 9};
Sorter sorter;
sorter.setStrategy(std::make_unique<QuickSort>());
sorter.executeSort(data); // 使用快速排序
sorter.setStrategy(std::make_unique<MergeSort>());
sorter.executeSort(data); // 切换为归并排序
2.2 基于函数指针的实现
对于简单策略,C++可以直接使用函数指针,避免虚函数开销:
cpp复制using SortingFunction = void (*)(std::vector<int>&);
void quickSort(std::vector<int>& data) {
// 快速排序实现
}
void mergeSort(std::vector<int>& data) {
// 归并排序实现
}
class Sorter {
public:
void setStrategy(SortingFunction func) {
strategy_ = func;
}
void executeSort(std::vector<int>& data) {
if (strategy_) {
strategy_(data);
}
}
private:
SortingFunction strategy_ = nullptr;
};
2.3 基于std::function的现代C++实现
C++11引入的std::function提供了更灵活的策略封装方式:
cpp复制class Sorter {
public:
using Strategy = std::function<void(std::vector<int>&)>;
void setStrategy(Strategy strategy) {
strategy_ = std::move(strategy);
}
void executeSort(std::vector<int>& data) {
if (strategy_) {
strategy_(data);
}
}
private:
Strategy strategy_;
};
这种实现允许使用lambda表达式、函数对象等各种可调用对象作为策略:
cpp复制Sorter sorter;
// 使用lambda表达式作为策略
sorter.setStrategy([](std::vector<int>& data) {
std::sort(data.begin(), data.end());
});
// 使用函数对象作为策略
struct CustomSort {
void operator()(std::vector<int>& data) const {
// 自定义排序逻辑
}
};
sorter.setStrategy(CustomSort{});
3. 策略模式在C++项目中的实战应用
3.1 游戏开发中的行为策略
在游戏开发中,策略模式可以优雅地处理各种游戏实体的行为变化。比如NPC的不同AI行为:
cpp复制class AIBehavior {
public:
virtual ~AIBehavior() = default;
virtual void update(NPC& npc) = 0;
};
class AggressiveBehavior : public AIBehavior {
public:
void update(NPC& npc) override {
// 攻击最近的玩家
}
};
class DefensiveBehavior : public AIBehavior {
public:
void update(NPC& npc) override {
// 寻找掩体并治疗
}
};
class NPC {
public:
void setBehavior(std::unique_ptr<AIBehavior> behavior) {
behavior_ = std::move(behavior);
}
void update() {
if (behavior_) {
behavior_->update(*this);
}
}
private:
std::unique_ptr<AIBehavior> behavior_;
};
3.2 金融计算中的定价策略
金融领域经常需要切换不同的定价模型:
cpp复制class PricingStrategy {
public:
virtual ~PricingStrategy() = default;
virtual double calculate(const Trade& trade) = 0;
};
class BlackScholesModel : public PricingStrategy {
public:
double calculate(const Trade& trade) override {
// Black-Scholes模型实现
}
};
class MonteCarloModel : public PricingStrategy {
public:
double calculate(const Trade& trade) override {
// 蒙特卡洛模拟实现
}
};
class PricingEngine {
public:
void setStrategy(std::unique_ptr<PricingStrategy> strategy) {
strategy_ = std::move(strategy);
}
double priceTrade(const Trade& trade) {
if (!strategy_) {
throw std::runtime_error("Pricing strategy not set");
}
return strategy_->calculate(trade);
}
private:
std::unique_ptr<PricingStrategy> strategy_;
};
3.3 网络通信中的协议策略
处理不同网络协议时,策略模式也非常有用:
cpp复制class ProtocolHandler {
public:
virtual ~ProtocolHandler() = default;
virtual void handle(Packet packet) = 0;
};
class HttpHandler : public ProtocolHandler {
public:
void handle(Packet packet) override {
// HTTP协议处理
}
};
class WebSocketHandler : public ProtocolHandler {
public:
void handle(Packet packet) override {
// WebSocket协议处理
}
};
class NetworkManager {
public:
void setHandler(std::unique_ptr<ProtocolHandler> handler) {
handler_ = std::move(handler);
}
void processPacket(Packet packet) {
if (handler_) {
handler_->handle(packet);
}
}
private:
std::unique_ptr<ProtocolHandler> handler_;
};
4. C++策略模式的高级技巧与优化
4.1 策略对象的生命周期管理
在C++中,策略对象的生命周期管理需要特别注意。我推荐以下几种方式:
- unique_ptr所有权转移(推荐):
cpp复制context.setStrategy(std::make_unique<ConcreteStrategy>());
- 共享所有权(当需要多个上下文共享策略时):
cpp复制auto strategy = std::make_shared<ConcreteStrategy>();
context1.setStrategy(strategy);
context2.setStrategy(strategy);
- 引用传递(当策略由外部管理时):
cpp复制ConcreteStrategy strategy;
context.setStrategy(strategy); // 需要确保strategy生命周期足够长
4.2 编译时策略(基于模板的策略模式)
C++模板允许我们在编译时确定策略,完全消除运行时开销:
cpp复制template <typename Strategy>
class Context {
public:
void execute() {
strategy_.execute();
}
private:
Strategy strategy_;
};
struct FastStrategy {
void execute() { /* 快速实现 */ }
};
struct SafeStrategy {
void execute() { /* 安全实现 */ }
};
// 使用
Context<FastStrategy> fastContext;
Context<SafeStrategy> safeContext;
这种方式的缺点是策略不能在运行时动态切换。
4.3 策略模式的性能考量
在性能敏感的场景中,需要考虑策略模式的性能影响:
- 虚函数调用开销:每次策略方法调用都有一次间接跳转
- 缓存不友好:策略对象可能分散在内存各处
- 分支预测失败:频繁切换不同策略可能导致分支预测失败
优化建议:
- 对高频调用的策略,考虑模板策略
- 确保策略对象紧凑排列(使用连续内存)
- 避免在紧密循环中频繁切换策略
4.4 策略模式与其他模式的结合
策略模式常与其他模式结合使用:
- 策略+工厂模式:
cpp复制class StrategyFactory {
public:
static std::unique_ptr<Strategy> create(StrategyType type) {
switch (type) {
case StrategyType::A: return std::make_unique<StrategyA>();
case StrategyType::B: return std::make_unique<StrategyB>();
default: throw std::invalid_argument("Unknown strategy type");
}
}
};
- 策略+享元模式:当策略无状态时,可以共享策略实例
- 策略+装饰器模式:对策略进行功能增强
5. 策略模式在C++中的常见问题与解决方案
5.1 策略接口膨胀问题
随着需求变化,策略接口可能变得越来越庞大:
cpp复制class BadStrategy {
public:
virtual void operation1() = 0;
virtual void operation2() = 0;
// ...更多操作
virtual void operationN() = 0;
};
解决方案:
- 遵循接口隔离原则,拆分为多个小接口
- 使用参数对象封装多个参数
cpp复制struct OperationParams {
int param1;
std::string param2;
// ...
};
class GoodStrategy {
public:
virtual void execute(const OperationParams& params) = 0;
};
5.2 策略与上下文的双向通信
有时策略需要访问上下文信息:
cpp复制class Context;
class Strategy {
public:
virtual void execute(Context& context) = 0;
};
class Context {
public:
void setStrategy(std::unique_ptr<Strategy> strategy) {
strategy_ = std::move(strategy);
}
void executeStrategy() {
if (strategy_) {
strategy_->execute(*this);
}
}
// 提供上下文信息的方法
int getInfo() const { return info_; }
private:
std::unique_ptr<Strategy> strategy_;
int info_;
};
5.3 默认策略实现
有时需要提供策略的默认实现:
cpp复制class DefaultStrategy : public Strategy {
public:
void execute() override {
// 默认实现
}
};
class Context {
public:
Context() : strategy_(std::make_unique<DefaultStrategy>()) {}
// ...其他方法
private:
std::unique_ptr<Strategy> strategy_;
};
5.4 策略模式的测试策略
策略模式使单元测试更加容易:
cpp复制TEST(StrategyTest, TestConcreteStrategy) {
MockStrategy mockStrategy;
EXPECT_CALL(mockStrategy, execute()).Times(1);
Context context;
context.setStrategy(mockStrategy);
context.executeStrategy();
}
TEST(ContextTest, TestDefaultBehavior) {
Context context;
// 测试默认策略行为
}
6. C++策略模式的最佳实践
根据我的项目经验,总结出以下最佳实践:
-
策略命名:策略类名应明确表达其行为特征,如FastSortStrategy、StableSortStrategy
-
策略粒度:每个策略应该只负责一个明确定义的行为,避免"上帝策略"
-
策略无状态:尽可能设计无状态的策略类,便于共享和测试
-
接口设计:
- 策略接口应该足够抽象,不暴露实现细节
- 参数和返回值应该使用通用类型
-
现代C++特性:
- 使用unique_ptr/shared_ptr管理策略对象生命周期
- 考虑使用std::function增加灵活性
- 对性能关键路径,考虑模板策略
-
文档规范:
- 明确说明每个策略的适用场景和前置条件
- 记录策略之间的差异和性能特征
-
错误处理:
- 策略接口应该明确错误处理方式(异常/错误码)
- 文档化策略可能抛出的异常
-
线程安全:
- 如果策略会被多线程使用,明确其线程安全要求
- 无状态策略通常是线程安全的
7. 从设计模式到设计原则
策略模式体现了几个重要的面向对象设计原则:
-
开闭原则(OCP):通过扩展策略来增加新行为,而非修改现有代码
-
单一职责原则(SRP):每个策略类只负责一个算法或行为
-
依赖倒置原则(DIP):高层模块依赖抽象接口,而非具体实现
-
接口隔离原则(ISP):策略接口应该小而专注
在实际项目中,我发现过度使用策略模式也会导致类爆炸问题。当策略非常简单(比如只有一两行代码)时,使用函数对象或lambda可能更合适。
8. C++策略模式与其他语言的对比
与其他语言相比,C++实现策略模式有其独特之处:
-
与Java对比:
- Java有接口关键字,C++使用抽象类
- Java的垃圾回收简化了策略对象生命周期管理
- C++可以使用模板实现编译时策略
-
与Python对比:
- Python的鸭子类型使得策略模式更灵活
- C++的策略模式性能更高
- Python缺少C++的const正确性检查
-
与Go对比:
- Go的接口是隐式实现的,更灵活
- C++的策略模式可以通过模板优化性能
- Go没有构造函数/析构函数的概念
在C++中实现策略模式时,我们需要特别注意内存管理、异常安全和性能优化,这些在其他语言中可能不是问题。
9. 实际项目中的经验教训
在我参与的一个高频交易系统项目中,策略模式的应用给了我们几个重要教训:
-
策略切换成本:在极端性能敏感的场景中,策略切换本身可能成为瓶颈。我们最终为某些核心路径实现了无分支的策略调度。
-
策略组合:有时需要组合多个策略。我们实现了策略的组合模式:
cpp复制class CompositeStrategy : public Strategy {
public:
void addStrategy(std::unique_ptr<Strategy> strategy) {
strategies_.push_back(std::move(strategy));
}
void execute() override {
for (auto& strategy : strategies_) {
strategy->execute();
}
}
private:
std::vector<std::unique_ptr<Strategy>> strategies_;
};
- 策略配置:从配置文件动态加载策略时,需要安全的策略工厂:
cpp复制std::unique_ptr<Strategy> createStrategy(const std::string& name) {
static const std::unordered_map<std::string,
std::function<std::unique_ptr<Strategy>()>> creators = {
{"fast", []() { return std::make_unique<FastStrategy>(); }},
{"safe", []() { return std::make_unique<SafeStrategy>(); }}
};
auto it = creators.find(name);
return it != creators.end() ? it->second() : nullptr;
}
- 策略验证:特别是金融领域的策略,需要严格的验证流程:
cpp复制class ValidatingStrategy : public Strategy {
public:
explicit ValidatingStrategy(std::unique_ptr<Strategy> strategy)
: strategy_(std::move(strategy)) {}
void execute() override {
if (!validate()) {
throw std::runtime_error("Strategy validation failed");
}
strategy_->execute();
}
private:
bool validate() const {
// 验证逻辑
return true;
}
std::unique_ptr<Strategy> strategy_;
};
10. C++策略模式的未来演进
随着C++标准的发展,策略模式也有新的实现方式:
- C++20概念约束的策略模板:
cpp复制template <typename S>
concept StrategyConcept = requires(S s) {
{ s.execute() } -> std::same_as<void>;
};
template <StrategyConcept Strategy>
class Context {
// ...
};
- 使用std::variant的类型安全策略:
cpp复制using StrategyVariant = std::variant<FastStrategy, SafeStrategy>;
class Context {
public:
void setStrategy(StrategyVariant strategy) {
strategy_ = std::move(strategy);
}
void executeStrategy() {
std::visit([](auto&& s) { s.execute(); }, strategy_);
}
private:
StrategyVariant strategy_;
};
- 协程友好的策略模式:
cpp复制class AsyncStrategy {
public:
virtual ~AsyncStrategy() = default;
virtual std::future<void> executeAsync() = 0;
};
在实际项目中采用这些新技术时,需要权衡可读性、维护性和性能收益。
