1. 策略模式在C++中的实战应用
作为一名有十年C++开发经验的老兵,我见过太多因为设计不当而变得难以维护的代码库。今天要聊的策略模式,正是解决这类问题的利器之一。策略模式属于行为型设计模式,它允许在运行时选择算法或行为,将算法与使用算法的对象解耦。这种模式特别适合那些需要根据不同条件执行不同算法变体的场景。
在实际项目中,策略模式最常见的应用场景包括:
- 支付系统(不同支付方式对应不同处理逻辑)
- 游戏AI(不同难度级别使用不同策略)
- 数据压缩(多种压缩算法可选)
- 排序算法(运行时选择排序策略)
提示:策略模式的核心价值在于它遵循了"开闭原则" - 对扩展开放,对修改关闭。这意味着你可以添加新策略而无需修改现有代码。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 策略模式的核心结构与实现
2.1 经典UML结构解析
策略模式的经典结构包含三个关键角色:
- Context(上下文):维护对策略对象的引用
- Strategy(抽象策略):定义算法接口
- ConcreteStrategy(具体策略):实现具体算法
在C++中,我们可以用抽象类或接口来定义Strategy,用具体类实现各种算法变体。Context类通过组合方式持有一个Strategy指针,通过这个指针调用具体算法。
2.2 C++实现示例
让我们用一个简单的税收计算例子来说明。假设我们需要计算不同国家的增值税:
cpp复制// 抽象策略类
class TaxStrategy {
public:
virtual ~TaxStrategy() = default;
virtual double calculate(double amount) const = 0;
};
// 具体策略类 - 中国增值税
class ChinaTax : public TaxStrategy {
public:
double calculate(double amount) const override {
return amount * 0.13; // 13%增值税
}
};
// 具体策略类 - 美国销售税
class USTax : public TaxStrategy {
public:
double calculate(double amount) const override {
return amount * 0.08; // 8%销售税
}
};
// 上下文类
class TaxCalculator {
private:
std::unique_ptr<TaxStrategy> strategy;
public:
explicit TaxCalculator(std::unique_ptr<TaxStrategy> strategy)
: strategy(std::move(strategy)) {}
void setStrategy(std::unique_ptr<TaxStrategy> newStrategy) {
strategy = std::move(newStrategy);
}
double computeTax(double amount) const {
if (!strategy) throw std::runtime_error("Strategy not set");
return strategy->calculate(amount);
}
};
2.3 现代C++的改进实现
C++11及以后版本提供了更多优雅的实现方式:
- 使用
std::function替代继承层次:
cpp复制class TaxCalculator {
private:
std::function<double(double)> strategy;
public:
explicit TaxCalculator(std::function<double(double)> strategy)
: strategy(std::move(strategy)) {}
double computeTax(double amount) const {
if (!strategy) throw std::runtime_error("Strategy not set");
return strategy(amount);
}
};
// 使用lambda表达式创建策略
TaxCalculator chinaCalc([](double amount) { return amount * 0.13; });
- 使用模板策略模式(编译时策略选择):
cpp复制template <typename TaxStrategy>
class TaxCalculator {
private:
TaxStrategy strategy;
public:
double computeTax(double amount) const {
return strategy.calculate(amount);
}
};
3. 策略模式的高级应用技巧
3.1 策略对象的创建与管理
在实际项目中,策略对象的生命周期管理是个重要问题。以下是几种常见做法:
- 策略工厂模式:
cpp复制class TaxStrategyFactory {
public:
static std::unique_ptr<TaxStrategy> create(const std::string& country) {
if (country == "CN") return std::make_unique<ChinaTax>();
if (country == "US") return std::make_unique<USTax>();
throw std::invalid_argument("Unsupported country");
}
};
- 策略缓存(享元模式):
cpp复制class StrategyCache {
private:
std::unordered_map<std::string, std::shared_ptr<TaxStrategy>> cache;
public:
std::shared_ptr<TaxStrategy> get(const std::string& key) {
auto it = cache.find(key);
if (it != cache.end()) return it->second;
auto strategy = TaxStrategyFactory::create(key);
auto result = cache.emplace(key, std::move(strategy));
return result.first->second;
}
};
3.2 策略组合与装饰器模式
有时我们需要组合多个策略。例如,计算总税时可能需要同时考虑增值税和附加税:
cpp复制class CombinedTaxStrategy : public TaxStrategy {
private:
std::vector<std::shared_ptr<TaxStrategy>> strategies;
public:
void addStrategy(std::shared_ptr<TaxStrategy> strategy) {
strategies.push_back(strategy);
}
double calculate(double amount) const override {
double total = 0.0;
for (const auto& strategy : strategies) {
total += strategy->calculate(amount);
}
return total;
}
};
3.3 策略模式的性能考量
策略模式会引入一定的运行时开销,主要体现在:
- 虚函数调用(通常1-2个时钟周期)
- 对象创建和销毁开销
- 可能的缓存不友好
在性能敏感的场景下,可以考虑以下优化:
- 使用模板策略(编译时多态)
- 对小对象使用自定义内存池
- 将策略设计为无状态(可共享实例)
4. 实战案例:游戏AI中的行为策略
让我们看一个更复杂的例子 - 游戏中的敌人AI。不同敌人类型在不同情境下会采用不同行为策略:
cpp复制// 行为策略接口
class BehaviorStrategy {
public:
virtual ~BehaviorStrategy() = default;
virtual void execute(GameObject& self, const GameState& state) = 0;
};
// 具体策略 - 攻击行为
class AttackStrategy : public BehaviorStrategy {
public:
void execute(GameObject& self, const GameState& state) override {
auto target = findNearestEnemy(self, state);
if (target) {
moveToward(self, *target);
if (inAttackRange(self, *target)) {
performAttack(self, *target);
}
}
}
};
// 具体策略 - 逃跑行为
class FleeStrategy : public BehaviorStrategy {
public:
void execute(GameObject& self, const GameState& state) override {
if (healthLow(self)) {
auto safeDirection = findSafeDirection(self, state);
moveInDirection(self, safeDirection);
}
}
};
// AI控制器(上下文)
class AIController {
private:
std::unique_ptr<BehaviorStrategy> strategy;
GameObject& controlledObject;
public:
AIController(GameObject& obj) : controlledObject(obj) {}
void setStrategy(std::unique_ptr<BehaviorStrategy> newStrategy) {
strategy = std::move(newStrategy);
}
void update(const GameState& state) {
if (strategy) {
strategy->execute(controlledObject, state);
}
}
};
在这个例子中,我们可以在运行时根据游戏状态动态改变AI行为:
cpp复制void updateAI(AIController& ai, const GameState& state) {
if (state.player.health < 30) {
ai.setStrategy(std::make_unique<AttackStrategy>());
} else {
ai.setStrategy(std::make_unique<FleeStrategy>());
}
}
5. 策略模式与其他设计模式的关系
5.1 策略模式 vs 状态模式
策略模式和状态模式在结构上相似,但意图不同:
- 策略模式:客户端主动选择算法
- 状态模式:状态转换由上下文或状态对象控制
5.2 策略模式 vs 模板方法模式
两者都用于算法变化,但:
- 模板方法:使用继承,在父类中定义算法骨架
- 策略模式:使用组合,完全替换算法实现
5.3 策略模式与命令模式结合
我们可以将策略模式与命令模式结合,创建可撤销的操作:
cpp复制class Command {
private:
std::function<void()> execute;
std::function<void()> undo;
public:
Command(std::function<void()> exec, std::function<void()> und)
: execute(exec), undo(und) {}
void executeCommand() { execute(); }
void undoCommand() { undo(); }
};
class CommandStrategy {
public:
virtual ~CommandStrategy() = default;
virtual Command createCommand() = 0;
};
6. 常见问题与解决方案
6.1 策略选择逻辑放在哪里?
常见选择:
- 客户端代码直接创建并设置策略
- 上下文类根据条件自动选择策略
- 使用专门的策略工厂
建议:对于简单条件判断,可以放在客户端;对于复杂逻辑,使用策略工厂更合适。
6.2 如何处理策略需要的额外数据?
解决方案:
- 通过构造函数或setter方法传入
- 让策略对象从上下文获取
- 使用依赖注入框架管理
6.3 如何测试策略模式?
测试策略模式相对简单:
- 单独测试每个具体策略
- 测试上下文类与策略的交互
- 使用模拟对象(Mock)测试策略选择逻辑
示例测试代码:
cpp复制TEST(TaxCalculatorTest, ChinaTaxCalculation) {
ChinaTax strategy;
EXPECT_DOUBLE_EQ(13.0, strategy.calculate(100.0));
}
TEST(TaxCalculatorTest, CalculatorWithStrategy) {
TaxCalculator calculator(std::make_unique<ChinaTax>());
EXPECT_DOUBLE_EQ(13.0, calculator.computeTax(100.0));
}
7. 实际项目中的经验教训
在多年的C++开发中,我总结了以下关于策略模式的经验:
-
避免策略膨胀:当策略类过多时,考虑使用其他模式(如规则引擎)替代
-
策略无状态化:尽可能设计无状态的策略对象,这样可以共享实例减少开销
-
接口设计原则:
- 保持策略接口精简
- 避免策略接口与上下文过度耦合
- 考虑使用
std::function简化接口
-
现代C++特性利用:
- 使用
std::unique_ptr管理策略生命周期 - 用lambda表达式实现简单策略
- 考虑使用模板策略实现编译时多态
- 使用
-
性能敏感场景的优化:
- 对频繁调用的策略,考虑内联关键代码
- 对小对象策略,使用局部缓冲避免频繁内存分配
- 对多线程环境,确保策略是线程安全的
最后分享一个真实案例:在一个交易系统中,我们最初为每种支付方式创建了独立的策略类。随着业务增长,策略类数量爆炸式增长。后来我们重构为基于规则的策略组合系统,使用少量基础策略和组合逻辑,使系统更易维护。
