1. 策略模式基础回顾与进阶必要性
在C++开发中,策略模式(Strategy Pattern)是我们最常用的设计模式之一。它定义了算法家族,分别封装起来,让它们之间可以互相替换。这种模式让算法的变化独立于使用算法的客户。但很多开发者停留在基础的"用接口定义策略"阶段,实际上策略模式在工程实践中有着更丰富的应用场景和优化空间。
我经历过一个游戏AI项目,角色行为策略最初采用简单if-else实现,随着策略数量增加到20+,代码变得难以维护。重构为策略模式后,不仅解决了维护性问题,还意外获得了运行时动态切换策略的能力。这个经历让我意识到,策略模式的价值远不止于教科书上的示例。
2. 策略模式的C++实现进阶技巧
2.1 现代C++中的策略实现选择
传统策略模式常通过抽象基类和继承体系实现。在现代C++中,我们有了更多选择:
// 传统面向对象实现 class SortingStrategy { public: virtual void sort(vector<int>& data) = 0; }; // 现代C++函数对象实现 using SortingStrategy = std::function<void(vector<int>&)>; // 模板策略实现 template<typename Strategy> class Sorter { Strategy strategy; public: void sort(vector<int>& data) { strategy(data); } };三种实现各有优劣:
- 面向对象实现:符合经典设计模式,支持运行时多态
- 函数对象:更灵活,可以接受lambda表达式
- 模板策略:零成本抽象,编译期多态
实际项目中,我推荐混合使用这些方法。对性能敏感的核心算法用模板策略,需要运行时动态切换的策略用函数对象。
2.2 策略的创建与管理
随着项目规模扩大,策略对象的创建和管理成为挑战。我总结了几种实用模式:
- 策略工厂模式:
class StrategyFactory { public: static std::unique_ptr<Strategy> create(const string& type) { if (type == "A") return make_unique<StrategyA>(); if (type == "B") return make_unique<StrategyB>(); throw invalid_argument("Unknown strategy type"); } };- 策略注册表模式:
class StrategyRegistry { static unordered_map<string, function<unique_ptr<Strategy>()>> creators; public: static void registerStrategy(const string& name, auto creator) { creators[name] = creator; } static unique_ptr<Strategy> create(const string& name) { return creators.at(name)(); } };- 策略缓存与重用:
class StrategyCache { unordered_map<string, shared_ptr<Strategy>> cache; public: shared_ptr<Strategy> get(const string& type) { if (!cache.count(type)) { cache[type] = StrategyFactory::create(type); } return cache[type]; } };3. 策略模式在复杂系统中的应用
3.1 多维度策略组合
在实际工程中,单一策略维度往往不够。比如游戏AI需要同时考虑移动策略和攻击策略。我常用两种处理方式:
- 策略组合模式:
class CompositeStrategy { shared_ptr<MoveStrategy> moveStrategy; shared_ptr<AttackStrategy> attackStrategy; public: void execute(Character& c) { moveStrategy->move(c); attackStrategy->attack(c); } };- 策略优先级系统:
class PriorityStrategy { vector<shared_ptr<Strategy>> strategies; public: void addStrategy(shared_ptr<Strategy> s, int priority) { strategies.emplace(priority, s); } void execute() { for (auto& [_, s] : strategies) { if (s->shouldExecute()) { s->execute(); break; } } } };3.2 策略的动态切换与过渡
平滑的策略切换能显著提升用户体验。在我的游戏项目中,实现了以下技术:
- 策略过渡混合:
class BlendingStrategy : public Strategy { shared_ptr<Strategy> from, to; float blendFactor = 0; public: void update() override { blendFactor += 0.1f; if (blendFactor >= 1.0f) { // 切换完成 } } void execute() override { auto resultFrom = from->evaluate(); auto resultTo = to->evaluate(); auto finalResult = lerp(resultFrom, resultTo, blendFactor); // 应用混合结果 } };- 策略历史堆栈:
class StrategyStack { stack<shared_ptr<Strategy>> stack; public: void push(shared_ptr<Strategy> s) { if (!stack.empty()) { stack.top()->onPause(); } stack.push(s); s->onEnter(); } void pop() { if (!stack.empty()) { stack.top()->onExit(); stack.pop(); if (!stack.empty()) { stack.top()->onResume(); } } } };4. 策略模式的性能优化
4.1 内存与缓存优化
在性能敏感场景,我采用以下优化手段:
- 策略内存池:
class StrategyPool { unordered_map<type_index, vector<unique_ptr<Strategy>>> pools; public: template<typename T> T* acquire() { auto& pool = pools[typeid(T)]; if (pool.empty()) { pool.push_back(make_unique<T>()); } auto ptr = pool.back().release(); pool.pop_back(); return static_cast<T*>(ptr); } template<typename T> void release(T* ptr) { pools[typeid(T)].emplace_back(ptr); } };- 策略数据局部性优化:
class HotColdStrategy { struct ColdData { // 不常访问的数据 }; struct HotData { // 高频访问的数据 }; unique_ptr<ColdData> cold; HotData hot; };4.2 编译期策略选择
对于编译期已知的策略,使用模板可以消除运行时开销:
template<typename Strategy> class Context { Strategy strategy; public: void execute() { strategy.doAlgorithm(); } }; // 使用 Context<QuickSortStrategy> context; context.execute();结合C++20概念(concepts)可以做得更好:
template<typename T> concept SortingStrategy = requires(T t, vector<int>& v) { { t.sort(v) } -> same_as<void>; }; template<SortingStrategy Strategy> class Sorter { // ... };5. 策略模式的测试与调试
5.1 策略的单元测试
为策略编写有效的测试用例:
TEST(StrategyTest, BubbleSortTest) { vector<int> data = {3,1,4,2}; BubbleSortStrategy strategy; strategy.sort(data); ASSERT_EQ(vector<int>{1,2,3,4}, data); } TEST(StrategyTest, StrategySwitchTest) { Context context(make_unique<StrategyA>()); context.execute(); context.setStrategy(make_unique<StrategyB>()); context.execute(); // 验证行为变化 }5.2 策略的调试技巧
- 策略日志记录:
class LoggingStrategyDecorator : public Strategy { shared_ptr<Strategy> wrapped; Logger& logger; public: void execute() override { logger.log("Executing strategy"); auto start = chrono::high_resolution_clock::now(); wrapped->execute(); auto end = chrono::high_resolution_clock::now(); logger.log("Strategy completed in " + to_string(chrono::duration_cast<milliseconds>(end-start).count()) + "ms"); } };- 策略可视化调试:
class DebuggableStrategy : public Strategy { public: virtual void debugDraw() = 0; }; void debugRender() { for (auto& strategy : activeStrategies) { strategy->debugDraw(); } }6. 策略模式在大型项目中的架构实践
6.1 策略的依赖管理
在复杂系统中,策略可能需要访问各种服务。我常用依赖注入:
class TradingStrategy { MarketDataService& marketData; OrderService& orderService; public: TradingStrategy(MarketDataService& mds, OrderService& os) : marketData(mds), orderService(os) {} void execute() { auto data = marketData.getLatest(); // 使用data生成订单 orderService.submit(order); } };6.2 策略的配置化
通过JSON等配置文件定义策略行为:
{ "strategies": { "aggressive": { "type": "TradingStrategy", "params": { "riskLevel": "high", "maxPosition": 0.3 } } } }对应的加载代码:
shared_ptr<Strategy> loadStrategy(const json& config) { auto type = config["type"]; if (type == "TradingStrategy") { return make_shared<TradingStrategy>( config["params"]["riskLevel"], config["params"]["maxPosition"] ); } // ... }7. 策略模式与其他模式的结合
7.1 策略与状态模式的异同
策略和状态模式结构相似但意图不同:
- 策略模式:客户端主动选择算法
- 状态模式:状态转换由内部条件决定
我有时会结合两者:
class HybridStateStrategy { shared_ptr<State> currentState; shared_ptr<Strategy> currentStrategy; void update() { // 状态逻辑决定策略选择 currentStrategy = currentState->selectStrategy(); currentStrategy->execute(); } };7.2 策略与命令模式的协同
策略模式定义算法,命令模式封装请求:
class CommandStrategyAdapter : public Command { shared_ptr<Strategy> strategy; public: void execute() override { strategy->execute(); } };这种组合在实现撤销/重做功能时特别有用。
8. 策略模式的现代C++演进
8.1 使用std::variant实现策略模式
C++17引入的variant提供了新思路:
using Strategy = variant<StrategyA, StrategyB, StrategyC>; class Context { Strategy strategy; public: void execute() { visit([](auto&& s) { s.execute(); }, strategy); } };8.2 策略模式与协程结合
C++20协程为策略模式带来新可能:
task<void> CoroutineStrategy::executeAsync() { while (!done) { auto data = co_await fetchDataAsync(); process(data); co_await delay(100ms); } }9. 策略模式的反模式与陷阱
9.1 过度设计警告
不是所有情况都需要策略模式。当遇到以下情况时,可能需要重新考虑:
- 只有1-2种简单算法变体
- 策略之间差异极小
- 策略很少变化
9.2 性能陷阱
动态策略的虚函数调用可能有开销。在性能关键路径上,考虑:
- 使用模板策略
- 将策略逻辑移出热循环
- 使用策略数据预计算
10. 实战案例:游戏AI策略系统
分享一个实际游戏项目中的策略系统架构:
class AICharacter { StrategyStack strategyStack; Blackboard sharedData; void update() { auto currentStrategy = strategyStack.top(); currentStrategy->evaluate(sharedData); currentStrategy->execute(sharedData); } }; class CombatStrategy : public Strategy { void evaluate(Blackboard& bb) override { if (bb.get<float>("health") < 0.3f) { bb.set("shouldEscape", true); } } void execute(Blackboard& bb) override { if (bb.get<bool>("shouldEscape")) { // 执行逃跑逻辑 } else { // 执行攻击逻辑 } } };这个系统支持:
- 策略优先级
- 策略中断
- 策略间数据共享
- 动态策略调整
在项目中,我们通过这套架构管理了超过50种AI行为策略,支持了复杂的NPC行为逻辑。