1. 策略模式在C++中的核心价值
策略模式是我在大型C++项目中用得最多的设计模式之一,它完美解决了算法频繁变更带来的维护噩梦。想象一下你正在开发一个电商系统,其中运费计算有十几种规则:普通快递、顺丰加急、满包邮、新用户首单减免......如果把这些算法全塞进订单类里,代码很快就会变成一锅粥。
策略模式的核心思想是把算法家族封装成独立的策略类,让它们可以相互替换。这种模式特别适合以下场景:
- 一个系统需要动态切换多种算法实现
- 有多个条件语句决定使用哪种算法变体
- 算法包含复杂逻辑或需要独立测试
重要提示:当发现类中有超过3个以上的条件分支处理同类问题时,就该考虑策略模式了
2. 经典策略模式实现剖析
2.1 基础架构三要素
标准的策略模式包含三个关键组件:
- 策略接口:定义算法族的通用操作(纯虚基类)
- 具体策略:实现接口的各种算法变体(派生类)
- 上下文类:持有策略引用并执行委派(组合关系)
// 策略接口 class SortingStrategy { public: virtual ~SortingStrategy() = default; virtual void sort(vector<int>& data) const = 0; }; // 具体策略A:升序排序 class AscendingSort : public SortingStrategy { public: void sort(vector<int>& data) const override { std::sort(data.begin(), data.end()); } }; // 具体策略B:降序排序 class DescendingSort : public SortingStrategy { public: void sort(vector<int>& data) const override { std::sort(data.begin(), data.end(), greater<int>()); } }; // 上下文类 class SortContext { private: unique_ptr<SortingStrategy> strategy_; public: explicit SortContext(unique_ptr<SortingStrategy>&& strategy = nullptr) : strategy_(move(strategy)) {} void setStrategy(unique_ptr<SortingStrategy>&& strategy) { strategy_ = move(strategy); } void executeSort(vector<int>& data) { if(strategy_) { strategy_->sort(data); } } };2.2 运行时策略切换的魔法
策略模式最强大的特性是允许在运行时动态更换算法。继续上面的排序例子:
vector<int> numbers = {3,1,4,1,5,9,2,6}; SortContext context(make_unique<AscendingSort>()); context.executeSort(numbers); // 升序排序 context.setStrategy(make_unique<DescendingSort>()); context.executeSort(numbers); // 降序排序这种动态切换能力在需要根据用户设置、系统状态等外部条件改变算法行为时特别有用。
3. 现代C++的进阶实现技巧
3.1 使用std::function替代继承
C++11之后,我们可以用函数对象简化策略模式:
class SortContext { private: function<void(vector<int>&)> strategy_; public: explicit SortContext(function<void(vector<int>&)> strategy = nullptr) : strategy_(move(strategy)) {} void setStrategy(function<void(vector<int>&)> strategy) { strategy_ = move(strategy); } void executeSort(vector<int>& data) { if(strategy_) { strategy_(data); } } }; // 使用lambda表达式作为策略 SortContext ctx([](vector<int>& data) { sort(data.begin(), data.end()); });这种方式减少了类层次结构,但牺牲了接口的明确性,适合简单策略场景。
3.2 策略工厂模式
当策略对象创建逻辑复杂时,可以引入工厂模式:
class StrategyFactory { public: static unique_ptr<SortingStrategy> create(const string& type) { if(type == "asc") return make_unique<AscendingSort>(); if(type == "desc") return make_unique<DescendingSort>(); throw invalid_argument("Unknown strategy type"); } }; auto strategy = StrategyFactory::create("asc"); SortContext context(move(strategy));4. 实战中的典型问题与解决方案
4.1 策略间共享数据问题
当多个策略需要访问公共数据时,常见的解决方案有:
- 通过上下文类传递共享数据
- 使用单例模式管理共享状态
- 将共享数据作为策略方法的参数
// 方案1:通过上下文共享 class Context { SharedData data_; //... void execute() { strategy_->execute(data_); } }; // 方案3:参数传递 class Strategy { public: virtual void execute(SharedData&) = 0; };4.2 策略组合模式
复杂场景下可能需要组合多个策略:
class CompositeStrategy : public SortingStrategy { vector<unique_ptr<SortingStrategy>> strategies_; public: void addStrategy(unique_ptr<SortingStrategy>&& strategy) { strategies_.push_back(move(strategy)); } void sort(vector<int>& data) const override { for(auto&& strategy : strategies_) { strategy->sort(data); } } };5. 性能优化与最佳实践
5.1 避免策略对象频繁创建
对于轻量级策略,可以考虑使用无状态策略:
class StatelessStrategy { public: static void sort(vector<int>& data) { // 实现代码 } }; // 使用时直接传递类类型 template<typename Strategy> void sortData(vector<int>& data) { Strategy::sort(data); }5.2 策略缓存机制
如果策略初始化成本高,可以实现策略缓存池:
class StrategyPool { unordered_map<string, unique_ptr<Strategy>> pool_; public: Strategy* get(const string& key) { if(!pool_.count(key)) { pool_[key] = createStrategy(key); } return pool_[key].get(); } };6. 测试策略模式的特殊技巧
测试策略模式时需要注意:
- 单独测试每个具体策略
- 测试上下文类与策略的交互
- 验证策略替换的正确性
TEST(SortingTest, StrategySwitch) { vector<int> data = {2,1,3}; SortContext ctx(make_unique<AscendingSort>()); ctx.executeSort(data); ASSERT_EQ(data, vector<int>{1,2,3}); ctx.setStrategy(make_unique<DescendingSort>()); ctx.executeSort(data); ASSERT_EQ(data, vector<int>{3,2,1}); }7. 与其他设计模式的配合
策略模式常与其他模式联用:
- 工厂模式:创建策略对象
- 装饰器模式:增强策略功能
- 模板方法模式:定义算法骨架
一个典型的组合案例:
class CachedStrategy : public Strategy { // 装饰器模式 unique_ptr<Strategy> wrapped_; mutable unordered_map<string, string> cache_; public: explicit CachedStrategy(unique_ptr<Strategy>&& wrapped) : wrapped_(move(wrapped)) {} string doAlgorithm(string_view input) const override { if(cache_.count(input.data())) { return cache_.at(input.data()); } auto result = wrapped_->doAlgorithm(input); cache_[input.data()] = result; return result; } };8. 真实项目中的经验教训
在金融交易系统中使用策略模式处理不同市场的数据解析时,我总结出几点关键经验:
- 接口设计要足够抽象:初期设计的策略接口过于具体,导致后续新增策略时需要频繁修改接口
- 考虑线程安全性:多线程环境下共享策略对象可能导致竞争条件
- 策略配置化:将策略选择逻辑配置化,避免硬编码
一个配置化的策略选择实现:
Strategy* createConfiguredStrategy(const Config& config) { auto type = config.get("strategy_type"); if(type == "fast") return new FastStrategy(config); if(type == "precise") return new PreciseStrategy(config); // ... }9. C++20下的新实现方式
C++20引入的概念(concepts)可以强化策略接口的约束:
template<typename T> concept SortingStrategy = requires(T s, vector<int>& data) { { s.sort(data) } -> same_as<void>; }; template<SortingStrategy T> class SortContext { T strategy_; public: void executeSort(vector<int>& data) { strategy_.sort(data); } };这种编译期接口检查比运行时多态更安全,但会损失一些灵活性。