三亩地 三亩地SAN MU DI · CODE DIARY
ARTICLE DETAIL

日记详情

真实记录编程学习的某一天,欢迎挑你感兴趣的翻一翻。

C++策略模式实战:游戏AI与支付系统设计

C++策略模式实战:游戏AI与支付系统设计

1. 策略模式在C++中的实战应用

在游戏开发中,我们经常遇到这样的场景:同一个角色在不同状态下需要执行不同的攻击行为。新手程序员可能会写出一堆if-else判断,而资深开发者则会掏出策略模式这把瑞士军刀。今天我们就来聊聊如何用C++实现这个经典设计模式。

策略模式的核心在于将算法家族封装起来,让它们可以互相替换。这种模式使得算法可以独立于使用它的客户端变化。在C++中,我们通常通过虚函数和智能指针来实现这种动态行为切换。不同于简单的函数指针,策略模式提供了更完整的面向对象解决方案。

2. 策略模式实现详解

2.1 基础类结构设计

我们先来看最经典的策略模式实现方式:

class Strategy { public: virtual ~Strategy() = default; virtual void execute() const = 0; }; class ConcreteStrategyA : public Strategy { public: void execute() const override { std::cout << "执行策略A" << std::endl; } }; class Context { private: std::unique_ptr<Strategy> strategy_; public: explicit Context(std::unique_ptr<Strategy> strategy) : strategy_(std::move(strategy)) {} void setStrategy(std::unique_ptr<Strategy> strategy) { strategy_ = std::move(strategy); } void executeStrategy() const { strategy_->execute(); } };

这种实现有几个关键点:

  1. 使用纯虚基类定义策略接口
  2. 通过unique_ptr管理策略对象生命周期
  3. Context类持有策略指针但不关心具体实现

2.2 现代C++改进方案

C++11之后,我们可以用function和lambda简化实现:

class Context { private: std::function<void()> strategy_; public: void setStrategy(std::function<void()> strategy) { strategy_ = strategy; } void executeStrategy() const { strategy_(); } }; // 使用示例 Context ctx; ctx.setStrategy([](){ std::cout << "Lambda策略" << std::endl; });

这种实现更灵活,但失去了接口的明确约束。在实际项目中,我建议根据团队规范选择合适的方式。

3. 实战应用场景

3.1 游戏AI行为切换

假设我们正在开发一个RPG游戏,敌人的AI行为需要根据玩家等级动态调整:

class EnemyAI { public: virtual ~EnemyAI() = default; virtual void update(Enemy& enemy) = 0; }; class AggressiveAI : public EnemyAI { void update(Enemy& enemy) override { // 追击玩家逻辑 } }; class DefensiveAI : public EnemyAI { void update(Enemy& enemy) override { // 防御逻辑 } }; class Enemy { std::unique_ptr<EnemyAI> ai_; public: void setAI(std::unique_ptr<EnemyAI> ai) { ai_ = std::move(ai); } void update() { ai_->update(*this); } };

3.2 支付系统设计

电商平台的支付模块是策略模式的典型应用场景:

class PaymentStrategy { public: virtual bool pay(double amount) = 0; }; class AlipayStrategy : public PaymentStrategy { bool pay(double amount) override { // 支付宝支付实现 } }; class WechatPayStrategy : public PaymentStrategy { bool pay(double amount) override { // 微信支付实现 } }; class PaymentService { std::unique_ptr<PaymentStrategy> strategy_; public: void setStrategy(std::unique_ptr<PaymentStrategy> strategy) { strategy_ = std::move(strategy); } bool processPayment(double amount) { return strategy_->pay(amount); } };

4. 性能优化与陷阱规避

4.1 对象创建开销

频繁创建策略对象可能导致性能问题。解决方案:

  1. 使用对象池模式
  2. 设计无状态策略(所有数据通过参数传入)
  3. 考虑flyweight模式共享策略实例
class StrategyPool { std::unordered_map<std::string, std::shared_ptr<Strategy>> pool_; public: std::shared_ptr<Strategy> get(const std::string& key) { if (!pool_.count(key)) { if (key == "A") pool_[key] = std::make_shared<ConcreteStrategyA>(); // 其他策略初始化 } return pool_[key]; } };

4.2 多线程安全问题

策略对象如果在多线程环境下使用需要特别注意:

  1. 确保策略类的成员函数是线程安全的
  2. 策略切换时需要适当的同步机制
  3. 避免在策略执行过程中修改策略
class ThreadSafeContext { std::mutex mtx_; std::unique_ptr<Strategy> strategy_; public: void setStrategy(std::unique_ptr<Strategy> strategy) { std::lock_guard<std::mutex> lock(mtx_); strategy_ = std::move(strategy); } void executeStrategy() const { std::lock_guard<std::mutex> lock(mtx_); strategy_->execute(); } };

5. 策略模式与其他模式的结合

5.1 策略+工厂模式

通过工厂类创建策略对象,进一步解耦:

class StrategyFactory { public: static std::unique_ptr<Strategy> create(const std::string& type) { if (type == "A") return std::make_unique<ConcreteStrategyA>(); if (type == "B") return std::make_unique<ConcreteStrategyB>(); throw std::invalid_argument("未知策略类型"); } }; // 使用示例 auto strategy = StrategyFactory::create("A"); Context ctx(std::move(strategy));

5.2 策略+装饰器模式

为策略添加额外功能而不修改原有代码:

class LoggingDecorator : public Strategy { std::unique_ptr<Strategy> wrapped_; public: explicit LoggingDecorator(std::unique_ptr<Strategy> strategy) : wrapped_(std::move(strategy)) {} void execute() const override { std::cout << "开始执行策略" << std::endl; wrapped_->execute(); std::cout << "策略执行完成" << std::endl; } }; // 使用示例 auto baseStrategy = std::make_unique<ConcreteStrategyA>(); auto decorated = std::make_unique<LoggingDecorator>(std::move(baseStrategy));

6. 测试策略模式

为策略类编写单元测试时要注意:

  1. 测试每个具体策略的独立行为
  2. 测试上下文类是否正确调用策略
  3. 测试策略切换功能
TEST(StrategyTest, ConcreteStrategyA_Execute_OutputsCorrectMessage) { ConcreteStrategyA strategy; testing::internal::CaptureStdout(); strategy.execute(); std::string output = testing::internal::GetCapturedStdout(); EXPECT_EQ(output, "执行策略A\n"); } TEST(ContextTest, ExecuteStrategy_CallsCurrentStrategy) { MockStrategy mockStrategy; EXPECT_CALL(mockStrategy, execute()).Times(1); Context ctx(std::make_unique<MockStrategyWrapper>(&mockStrategy)); ctx.executeStrategy(); }

7. 实际项目中的经验分享

在大型项目中使用策略模式时,我总结了几点经验:

  1. 接口设计:策略接口要足够通用,但不要过度设计。我曾在项目中设计了一个包含20多个方法的策略接口,结果维护起来非常痛苦。

  2. 生命周期管理:在长时间运行的服务中,要注意策略对象的内存泄漏问题。使用智能指针可以避免大部分问题。

  3. 配置化:将策略选择逻辑配置化,可以通过配置文件或数据库动态调整策略,而不需要重新编译代码。

  4. 日志记录:为策略切换添加日志记录,这在调试复杂问题时非常有用。

  5. 性能分析:策略模式会带来一定的运行时开销,在性能敏感的场景要做基准测试。我们曾用Google Benchmark测试发现虚函数调用在极端情况下会成为瓶颈,后来改用std::function取得了更好的性能。

// 性能测试示例 static void BM_VirtualStrategy(benchmark::State& state) { Context ctx(std::make_unique<ConcreteStrategyA>()); for (auto _ : state) { ctx.executeStrategy(); } } BENCHMARK(BM_VirtualStrategy); static void BM_FunctionStrategy(benchmark::State& state) { Context ctx([](){ /* lambda实现 */ }); for (auto _ : state) { ctx.executeStrategy(); } } BENCHMARK(BM_FunctionStrategy);

8. 策略模式的替代方案

虽然策略模式很强大,但并不是所有情况都适用:

  1. 简单条件逻辑:如果只有2-3个简单分支,if-else可能更直接
  2. 编译时决策:如果策略在编译时就能确定,考虑使用模板
  3. 性能极端敏感:虚函数调用有开销,在这种情况下可以考虑策略类的CRTP实现
template <typename T> class StrategyBase { public: void execute() const { static_cast<const T*>(this)->executeImpl(); } }; class ConcreteStrategy : public StrategyBase<ConcreteStrategy> { friend class StrategyBase<ConcreteStrategy>; void executeImpl() const { // 具体实现 } };

这种编译期多态避免了虚函数开销,但失去了运行时动态切换的能力。

← 返回列表