1. 策略模式在C++中的实战应用
在游戏开发中,我们经常遇到这样的场景:同一个角色在不同状态下需要执行不同的攻击行为。新手程序员可能会写出一长串if-else判断,而资深开发者则会搬出策略模式这把"瑞士军刀"。今天我们就来聊聊这个看似简单却威力十足的设计模式。
策略模式(Strategy Pattern)属于行为型模式,它定义了一系列算法,将每个算法封装起来,并使它们可以相互替换。这种模式让算法的变化独立于使用算法的客户。在C++中,我们可以通过虚函数、函数指针、模板等多种方式实现策略模式,每种实现都有其独特的适用场景。
2. 策略模式的核心思想与实现方式
2.1 经典虚函数实现
最传统的实现方式是使用抽象基类和继承体系:
class AttackStrategy { public: virtual void execute() = 0; virtual ~AttackStrategy() = default; }; class NormalAttack : public AttackStrategy { public: void execute() override { std::cout << "执行普通攻击" << std::endl; } }; class CriticalAttack : public AttackStrategy { public: void execute() override { std::cout << "暴击!双倍伤害" << std::endl; } }; class Character { std::unique_ptr<AttackStrategy> strategy; public: void setStrategy(std::unique_ptr<AttackStrategy>&& newStrategy) { strategy = std::move(newStrategy); } void attack() { if(strategy) strategy->execute(); } };这种实现方式的优势在于:
- 符合开闭原则,新增策略无需修改现有代码
- 通过基类指针实现运行时多态
- 内存管理清晰(使用智能指针)
2.2 现代C++的函数对象实现
C++11之后,我们可以用更简洁的函数对象方式:
class Character { std::function<void()> strategy; public: template<typename T> void setStrategy(T&& newStrategy) { strategy = std::forward<T>(newStrategy); } void attack() { if(strategy) strategy(); } }; // 使用lambda表达式 character.setStrategy([](){ std::cout << "闪电链攻击" << std::endl; });这种方式的优势:
- 无需继承体系,减少类爆炸
- 支持lambda表达式,代码更紧凑
- 编译期多态,性能更好
3. 策略模式在游戏开发中的典型应用
3.1 AI行为控制
在NPC AI系统中,我们可以为不同的行为状态(巡逻、追击、逃跑)定义策略类:
class AIController { std::unique_ptr<BehaviorStrategy> currentBehavior; public: void update() { if(currentBehavior) currentBehavior->execute(); } void setBehavior(std::unique_ptr<BehaviorStrategy>&& newBehavior) { currentBehavior = std::move(newBehavior); } };3.2 伤害计算系统
不同武器类型可以使用不同的伤害计算策略:
class DamageCalculator { std::unique_ptr<DamageStrategy> strategy; public: int calculateDamage(Character& attacker, Character& target) { return strategy->calculate(attacker, target); } };4. 策略模式的高级应用技巧
4.1 策略组合模式
有时我们需要组合多个策略:
class CompositeStrategy : public AttackStrategy { std::vector<std::unique_ptr<AttackStrategy>> strategies; public: void addStrategy(std::unique_ptr<AttackStrategy>&& strategy) { strategies.push_back(std::move(strategy)); } void execute() override { for(auto& s : strategies) { s->execute(); } } };4.2 策略工厂模式
结合工厂模式创建策略对象:
class StrategyFactory { public: static std::unique_ptr<AttackStrategy> create(const std::string& type) { if(type == "fire") return std::make_unique<FireAttack>(); if(type == "ice") return std::make_unique<IceAttack>(); throw std::runtime_error("Unknown strategy type"); } };5. 性能优化与注意事项
5.1 虚函数开销分析
虚函数调用会有以下开销:
- 通过vtable间接跳转(约3-5个时钟周期)
- 阻碍编译器内联优化
- 可能导致分支预测失败
解决方案:
- 对小而频繁调用的策略,考虑模板策略模式
- 使用final关键字修饰不需要再被继承的策略类
5.2 内存管理最佳实践
推荐做法:
// 使用unique_ptr管理策略生命周期 std::unique_ptr<Strategy> strategy = std::make_unique<ConcreteStrategy>(); // 如果需要共享策略 std::shared_ptr<Strategy> sharedStrategy = std::make_shared<ConcreteStrategy>();避免:
// 原始指针容易导致内存泄漏 Strategy* strategy = new ConcreteStrategy();6. 策略模式与其他模式的协作
6.1 策略与状态模式的区别
虽然结构相似,但两者意图不同:
- 策略模式:客户端主动选择算法
- 状态模式:状态转换由上下文自动处理
6.2 策略与模板方法模式
模板方法模式:
- 在父类中定义算法骨架
- 子类实现特定步骤
策略模式:
- 完全替换整个算法
- 更具灵活性
7. 实际项目中的经验教训
7.1 策略爆炸问题
当策略类过多时会导致:
- 代码难以维护
- 类间关系复杂化
解决方案:
- 使用策略工厂统一管理
- 考虑将简单策略合并
- 使用lambda表达式替代简单策略类
7.2 跨DLL边界问题
在Windows平台,当策略类和上下文分属不同DLL时:
- 需要统一内存分配/释放方式
- 建议使用接口类而非具体实现类
示例解决方案:
// 在头文件中定义接口 class IStrategy { public: virtual void execute() = 0; virtual ~IStrategy() = default; }; // 导出创建函数 extern "C" __declspec(dllexport) IStrategy* createStrategy();8. C++20中的新特性应用
8.1 概念约束的策略模板
template<typename T> concept AttackStrategy = requires(T t) { { t.execute() } -> std::same_as<void>; }; template<AttackStrategy S> class Character { S strategy; public: void attack() { strategy.execute(); } };8.2 协程与策略模式
结合C++20协程实现异步策略:
class AsyncStrategy { public: std::future<void> executeAsync() { co_await std::suspend_always{}; // 异步执行策略 } };9. 测试策略模式的技巧
9.1 模拟策略对象
使用gmock框架测试策略调用:
class MockStrategy : public AttackStrategy { public: MOCK_METHOD(void, execute, (), (override)); }; TEST(CharacterTest, AttackUsesStrategy) { MockStrategy mock; Character character; EXPECT_CALL(mock, execute()); character.setStrategy(std::make_unique<MockStrategy>(mock)); character.attack(); }9.2 性能测试方法
比较不同实现方式的性能:
void benchmark() { // 测试虚函数实现 { VirtualStrategy strategy; auto start = std::chrono::high_resolution_clock::now(); for(int i = 0; i < 1000000; ++i) { strategy.execute(); } auto end = std::chrono::high_resolution_clock::now(); std::cout << "Virtual: " << (end-start).count() << "ns\n"; } // 测试函数指针实现 { FunctionPointerStrategy strategy; auto start = std::chrono::high_resolution_clock::now(); for(int i = 0; i < 1000000; ++i) { strategy.execute(); } auto end = std::chrono::high_resolution_clock::now(); std::cout << "Function pointer: " << (end-start).count() << "ns\n"; } }10. 从设计模式到设计原则
策略模式体现了以下设计原则:
- 开闭原则(对扩展开放,对修改关闭)
- 单一职责原则(每个策略类只负责一个算法)
- 依赖倒置原则(依赖抽象而非具体实现)
在实际项目中,我发现策略模式特别适合以下场景:
- 需要动态切换算法
- 有多个相似的类只在行为上不同
- 需要隔离复杂的业务规则
最后分享一个实用技巧:在Visual Studio中,可以通过设置"继承关系"窗口来可视化策略类层次结构,这对理解复杂策略体系特别有帮助。