资讯动态

C++策略模式完整源码解析:从if-else到优雅可扩展设计

发布时间:2026/10/22 6:06:14 来源:尧图企业网站定制
说句实话策略模式可能是所有设计模式里看起来最简单、代码最容易写歪的一个。大多数人背得住定义——将一组算法封装起来使它们可以互相替换——但真到了C项目里写出来的往往是虚函数满天飞、生命周期没人管、换个策略还要改客户端调用的伪策略模式。这篇文章我打算把C实现策略模式的完整源代码从头到尾过一遍从策略接口、具体策略、上下文类到客户端调用全部贴出来配合编译运行验证和工程化层面的注意事项把这块一次讲透。写这篇文章的缘起是我在某次代码评审里看到一段超过两百行的if-else链被调用的模块本身只做一件事根据用户类型计算订单价格。问题在于每加一种用户类型就必须改动那段核心计算函数测试要重跑评审人要看半天。这其实就是典型的策略模式该上场了的信号而且像这种情况在业务系统里实在太常见了。所以我决定把整套实现整理成文顺便把我在实际项目中踩过的坑、做过的取舍一并交代清楚这样对正在学习设计模式的人和已经在项目里用策略模式的人都有参考价值。1. 先理解策略模式解决的是什么一段失控的if-else链1.1 大多数人的第一版switch-case里堆算法假设我们现在要做一个订单计价模块需求很简单普通用户原价会员用户打8折促销活动用户满300减50很多人的第一反应是写一个函数集中在里面做判断// bad_example.cpp double calculatePrice(UserType type, double basePrice) { if (type UserType::NORMAL) { return basePrice; } else if (type UserType::MEMBER) { return basePrice * 0.8; } else if (type UserType::PROMOTION) { return basePrice 300 ? basePrice - 50 : basePrice; } return basePrice; }这段代码在只有三种类型的时候完全没问题它短小、直观、一眼能看懂。但问题就是业务需求不会停留在三种类型。有一天产品经理过来说要加满200减20好你在else if后面再加一段过两天又说要加新人首单立减30你再加一段再过两周说不同城市不同折扣你把城市判断也塞进来。我当时在一个模拟项目X里就是眼睁睁看着这个函数从20行膨胀到200多行的。每次加规则都要在同一个函数里找出对应的判断分支稍微改错一个括号普通用户的价格可能就被会员折扣影响了。更难受的是测试一个函数有七八个条件分支分支之间互相嵌套你根本无法穷举所有组合每次改动都像是在排雷。1.2 这段代码到底哪里出了问题很多人会把问题归结为分支太多但这个结论其实太表面了。真正的核心问题是算法计价规则和使用算法的那个流程上下文逻辑被死死地耦合在同一个函数里。算法本身不是稳定的计价规则是业务里变化最频繁的部分每个月都可能调折扣、改满减门槛。调用流程是相对稳定的无论哪种计价规则做的事情都是传入原始价格算出一个最终价格。这两者的变化频率不一样却写在同一个函数里每一次算法变更都会迫使调用方重新编译、重新测试。这就是典型的违反开闭原则对扩展开放对修改关闭而上面的写法恰恰是对修改敞开的加一个规则就得改一次函数本体。1.3 策略模式的解法把变化的部分抽出来策略模式的思路其实特别直白既然计算价格这个动作有一堆不同的实现那就把这个动作本身定义成一个接口每种计价规则是一个独立的类都实现同一个接口。原来的那个大函数不再关心你是哪种用户它只对着这个接口说请你算一下价格至于具体按什么规则算由传入的策略对象决定。用这种方式重写计价逻辑之后变化的部分被隔离在独立的策略类里新增一种计价规则只需要新增一个类完全不需要改动原有核心逻辑。这也就是策略模式的本质定义一组算法把每个算法都封装到独立的类中让它们可以互相替换替换策略不会影响使用策略的客户端。2. 策略模式的三个角色与代码协作关系2.1 三个角色分别是谁策略模式的结构并不复杂一共就三个角色角色类名约定职责抽象策略IPriceStrategy/PriceStrategy定义算法家族的公共接口声明业务动作具体策略NormalStrategy/MemberStrategy实现具体的算法逻辑每个类是一个独立算法上下文OrderContext/PriceContext持有策略对象负责调用策略接口串联客户端与策略抽象策略是契约它规定了无论什么算法都必须提供同一个动作入口。具体策略是实现一个类对应一种算法。上下文是中间人它自己不实现算法只负责保管当前正在使用的策略对象并在客户端请求时把调用转交给策略对象。2.2 一次完整调用的协作流程我以订单计价为例把调用过程捋一遍客户端比如某个下单模块创建具体的策略对象例如MemberStrategy。客户端把策略对象设置到OrderContext上。客户端调用context.calculate(300.0)。OrderContext内部把参数转发给它持有的策略对象的calculate方法。策略对象运行自己的算法返回结果。OrderContext把结果返回给客户端。这个过程里客户端知道它创建了一个MemberStrategy但OrderContext完全不知道。上下文只依赖抽象策略接口这就让上下文和具体算法之间形成了松耦合算法怎么换上下文都不需要改一行代码。2.3 上下文不一定非自己是最后一个调用者这里有一个容易混淆的点上下文到底承担多大的职责我见过不少初学者把上下文本身做成一个巨大的业务服务里面除了策略还塞了数据库访问、日志记录、缓存逻辑。这其实已经超出了上下文本来的职责。在策略模式里上下文的职责有且只有两个保存客户端的策略对象或者叫维护状态。把客户端的请求委托给策略对象。至于客户端怎么决定用哪个策略、策略对象从哪里创建都不应该由上下文操心。如果再把策略创建逻辑也塞进上下文那上下文就会重新膨胀变成另一个两百行的if-else。通常的做法是让工厂类去决定创建哪个策略这一点在后面的章节里我会展开讲。3. C实现策略模式的三种路线对比3.1 经典多态方案教科书里的写法C里最符合策略模式原始定义的做法是用抽象基类加虚函数定义一个策略接口具体策略继承并实现接口上下文持有一个指向策略接口的指针或引用。// strategy_interface.h #pragma once class PriceStrategy { public: virtual ~PriceStrategy() default; virtual double calculate(double basePrice) const 0; };// normal_strategy.h #pragma once #include strategy_interface.h class NormalStrategy : public PriceStrategy { public: double calculate(double basePrice) const override { return basePrice; } };// member_strategy.h #pragma once #include strategy_interface.h class MemberStrategy : public PriceStrategy { public: double calculate(double basePrice) const override { return basePrice * 0.8; } };这种方案的好处是结构清晰每个具体策略都是一个有名有姓的类适合策略本身还有额外状态的场景比如策略里需要存储规则参数。坏处是需要额外的继承体系类数量会随着策略数量线性增加如果策略只是简单的lambda函数写这么多类就有点小题大做。3.2 std::function方案现代C的轻量替代如果你使用的是C11及以后的版本完全可以用std::function来替代抽象基类的角色策略从类变成可调用对象。// function_strategy_context.h #pragma once #include functional class OrderContext { public: using PriceStrategyFn std::functiondouble(double); explicit OrderContext(PriceStrategyFn strategy nullptr) : strategy_(std::move(strategy)) {} void setStrategy(PriceStrategyFn strategy) { strategy_ std::move(strategy); } double calculate(double basePrice) const { if (!strategy_) { throw std::runtime_error(strategy not set); } return strategy_(basePrice); } private: PriceStrategyFn strategy_; };客户端使用时可以传入lambda、函数指针、bind表达式或者重载了operator()的对象// main.cpp #include function_strategy_context.h #include iostream int main() { OrderContext ctx; ctx.setStrategy([](double price) { return price; }); // 普通 ctx.setStrategy([](double price) { return price * 0.8; }); // 会员 ctx.setStrategy([](double price) { return price 300 ? price - 50 : price; }); // 满减 std::cout ctx.calculate(400.0) std::endl; }这种写法极大减少了代码量策略不需要类定义lambda表达式直接描述算法。缺点也很明显策略没有类型名代码可读性在策略复杂时会下降而且lambda难以持久化保存状态如果策略需要多个参数或复杂初始化逻辑用lambda会别扭。3.3 模板策略方案编译期绑定第三种方案用模板在编译期确定策略类型完全绕开虚函数开销// template_strategy_context.h #pragma once template typename Strategy class TemplateOrderContext { public: explicit TemplateOrderContext(Strategy strategy) : strategy_(std::move(strategy)) {} double calculate(double basePrice) const { return strategy_.calculate(basePrice); } private: Strategy strategy_; };使用方式// main.cpp #include template_strategy_context.h #include member_strategy.h #include iostream int main() { TemplateOrderContextMemberStrategy ctx(MemberStrategy{}); std::cout ctx.calculate(400.0) std::endl; }模板方案性能最好因为策略在编译期就确定了没有虚函数间接调用。但代价是运行时替换策略的能力没了TemplateOrderContextStrategyA和TemplateOrderContextStrategyB是两种完全不同的类型你没法在运行时让同一个上下文对象从策略A切换到策略B。如果策略只在编译期确定模板方案是很好的选择但大部分业务场景恰恰需要运行时切换所以经典多态方案仍然是主流。3.4 我的选型建议选哪种方案核心看两个维度策略是否复杂是否需要运行时切换。场景推荐方案策略简单一两行的计算逻辑std::function lambda策略复杂带有状态和多个方法经典多态抽象基类性能敏感策略编译期已确定模板策略项目是C03老标准只能经典多态我个人的习惯是业务系统优先考虑经典多态因为它意图最明确、最容易维护工具类内部、框架代码里优先考虑std::function嵌入式或性能关键路径考虑模板方案。下面完整源码部分我以经典多态为主因为这是最不容易出错、最容易迁移到其他语言思路的写法。4. 完整源码拆解一个可直接编译运行的计价项目为了让整套代码真正能落地我用一个订单计费项目把完整源码梳理出来。这个项目包含三种计价策略普通原价、会员八折、满300减50。代码分为五个文件结构如下order_price/ ├── price_strategy.h ├── normal_strategy.h ├── member_strategy.h ├── promotion_strategy.h ├── order_context.h └── main.cpp4.1 策略接口约束所有算法的契约// price_strategy.h #pragma once class PriceStrategy { public: virtual ~PriceStrategy() default; virtual double calculate(double basePrice) const 0; };接口设计看起来简单但有三个点我想特别提醒第一析构函数必须是虚函数而且建议用 default而不是空实现。如果不把析构函数声明为虚函数通过基类指针删除派生类对象时会发生未定义行为这是C里非常经典的坑。第二虚函数建议加上const。calculate本身不修改策略对象的内部状态只是一个计算动作加const后非const和const对象都能调用也向调用方传达了这个动作不会改变对象的语义。第三建议使用override关键字。它能让编译器帮你检查是否真的覆盖了基类虚函数写错签名时立刻报错而不是静默地生成一个全新的函数。4.2 三种具体策略算法被封装成独立类// normal_strategy.h #pragma once #include price_strategy.h class NormalPriceStrategy : public PriceStrategy { public: double calculate(double basePrice) const override { return basePrice; } };// member_strategy.h #pragma once #include price_strategy.h class MemberPriceStrategy : public PriceStrategy { public: double calculate(double basePrice) const override { return basePrice * 0.8; } };// promotion_strategy.h #pragma once #include price_strategy.h class PromotionPriceStrategy : public PriceStrategy { public: explicit PromotionPriceStrategy(double threshold 300.0, double discount 50.0) : threshold_(threshold), discount_(discount) {} double calculate(double basePrice) const override { return basePrice threshold_ ? basePrice - discount_ : basePrice; } private: double threshold_; double discount_; };注意PromotionPriceStrategy的构造函数带了参数这是策略类可以携带状态的一个例子。满减策略的满300和减50这两个参数被封装在策略内部上下文和客户端都不需要知道具体怎么算只需要设置好策略对象即可。这样设计还有一个额外的好处如果满减规则从满300减50变成满200减20不需要修改上下文只需要在创建策略时传入新的参数甚至可以在运行时动态调整策略对象的内部参数。规则参数和规则逻辑被很好地聚合在了一起。4.3 上下文类持有策略并转发请求// order_context.h #pragma once #include memory #include stdexcept #include price_strategy.h class OrderContext { public: explicit OrderContext(std::unique_ptrPriceStrategy strategy nullptr) : strategy_(std::move(strategy)) {} void setStrategy(std::unique_ptrPriceStrategy strategy) { strategy_ std::move(strategy); } double calculate(double basePrice) const { if (!strategy_) { throw std::runtime_error(OrderContext::calculate: strategy is null); } return strategy_-calculate(basePrice); } private: std::unique_ptrPriceStrategy strategy_; };这里我在上下文里用了std::unique_ptrPriceStrategy而不是裸指针原因有三个所有权语义明确上下文负责管理策略对象的生命周期客户端设置完策略后就不用再管了上下文析构时策略对象会被自动回收。避免复制问题的纠缠策略对象通常不应该被拷贝用unique_ptr天然禁用拷贝只允许移动语义符合策略对象的资源管理预期。好记性不如烂笔头裸指针方案容易在客户端忘记delete或者上下文和外部双重deleteunique_ptr直接把这些错误从根上消灭掉。4.4 客户端调用与编译运行// main.cpp #include iostream #include memory #include normal_strategy.h #include member_strategy.h #include promotion_strategy.h #include order_context.h int main() { OrderContext context; context.setStrategy(std::make_uniqueNormalPriceStrategy()); std::cout Normal price: context.calculate(400.0) std::endl; context.setStrategy(std::make_uniqueMemberPriceStrategy()); std::cout Member price: context.calculate(400.0) std::endl; context.setStrategy(std::make_uniquePromotionPriceStrategy(300.0, 50.0)); std::cout Promotion price: context.calculate(400.0) std::endl; return 0; }编译命令C11及以上即可g -stdc11 -o order_price main.cpp运行结果Normal price: 400 Member price: 320 Promotion price: 350从输出上就能验证策略模式的效果同一个OrderContext对象在运行时先后被换上了三种不同策略计算方法完全不同但上下文和客户端调用代码本身不需要任何修改。新增一种计价策略只需要新增一个策略类并让客户端在创建处多写一行这就是这个模式最直接的价值。如果不想用unique_ptr也可以改成std::shared_ptr或者让上下文持有策略对象的裸引用但这三种方式的所有权语义、适用场景差别很大通常我会建议默认用unique_ptr只有当策略对象需要被多个上下文共享时才考虑shared_ptr。5. 工程化细节与常见坑从能跑到好用5.1 策略对象的生命周期管理策略对象到底应该由谁创建、谁销毁这个问题在经典多态方案里非常容易踩坑。我见过一个真实场景某模块在构造函数里直接new了一个策略对象然后把这个裸指针塞给上下文接着客户端又保存了这个指针等着后续使用。策略模式的多态本性决定了同一个策略对象可能被多个地方共用但是谁负责清理呢一旦上下文析构时 delete 了客户端那边就成了悬空指针如果客户端也 delete就会二次释放。所以我强烈建议采用明确的所有权策略上下文独占策略对象用unique_ptr创建之后立即转移所有权上下文析构即销毁。策略对象被共享用shared_ptr配合make_shared创建。策略对象生命周期完全由外部管理这是最危险的一种除非上下文只使用不拥有而且外部能绝对保证对象存活时间覆盖上下文使用窗口否则不要这么干。三种方式里默认选第一种因为它最简单也最不容易出错。5.2 析构函数为什么必须写成虚函数这是一个笔试面试都爱考、但在实际工程里经常被忽略的问题。如果PriceStrategy的析构函数不是虚函数那么当你执行std::unique_ptrPriceStrategy ptr std::make_uniquePromotionPriceStrategy();这段代码在ptr析构时会通过基类指针调用析构函数。如果基类析构函数不是虚函数调用结果就是未定义行为掉到PromotionPriceStrategy的成员变量threshold_和discount_根本不会被正确析构内存泄漏和资源泄漏接踵而至。所以凡是作为多态基类的类析构函数务必声明为虚函数这是C写设计模式的前提条件。5.3 const正确性接口设计里最容易漏的一点策略接口的成员函数要不要加const我的建议是对于不修改策略内部状态的纯计算动作一律加。加了const的好处是上下文持有策略对象的引用或指针时无论是通过const引用还是非const引用调用calculate都能正常编译。如果接口没加const那么一个const OrderContext对象的calculate就编译不过除非用mutable这种破坏语义的方式去绕那就更丑了。另一个相关细节是策略对象内部如果有配置参数比如满减阈值这些参数最好在构造函数里初始化并且不提供修改接口保持策略对象在业务计算过程中的不可变性这样可以避免策略在多个线程中共享时的数据竞争问题。5.4 头文件组织与编译依赖我之前见过有人把所有策略、上下文、客户端调用全写在一个main.cpp里跑是能跑但工程上维护起来非常痛苦。建议按一个类一个头文件的方式组织每个头文件只包含必要的头文件不要引入过多无关依赖。具体到策略模式的场景有几个头文件组织要点策略接口头文件price_strategy.h应当是不依赖任何具体策略的它是整个体系的基石。每个具体策略头文件只依赖策略接口头文件不要相互依赖。不同策略之间不应知道彼此的存在。上下文头文件依赖策略接口头文件但不包含任何具体策略头文件这是保证换策略不改上下文的关键。这套依赖关系的本质是具体策略在编译期与上下文解耦上下文只认识抽象接口不认识任何具体策略类。如果你发现上下文头文件里 include 了一堆具体策略的_strategy.h那基本可以断定上下文和具体算法的耦合又回来了。5.5 上线前一定要做的检查结合我自己的经验策略模式代码提交前通常过一遍下面几个检查点上下文是否能够通过空策略对象构造如果构造时策略为空调用时有没有抛异常——这决定了系统启动顺序是否安全。策略对象是否可拷贝如果不希望拷贝编译时有没有隐式生成拷贝构造函数——用unique_ptr后这个担忧自动消除。头文件依赖是否干净上下文模块是否可能因为新增具体策略而触发重新编译——如果会说明依赖关系没设计好。策略接口是否足够稳定一旦线上发布了接口后续所有新策略都要遵循这个接口改接口会造成全部策略类修改成本极高。6. 策略模式在真实项目里的适用与不适用场景6.1 适合用策略模式的信号我在项目里判断是否引入策略模式通常问自己三个问题这段代码是不是有一组可互换的算法或规则而且它们会在运行时被替换算法的变化频率是否高于算法的调用流程客户端是否只关心做什么不关心怎么做三个问题只要有两个是肯定回答那策略模式大概率是合适的。实际案例包括领域策略示例支付系统支付宝支付、微信支付、银行卡支付支付动作统一渠道逻辑各不相同压缩工具不同压缩算法zip、gzip、miniz对上层暴露的接口都是压缩/解压日志格式控制台输出、JSON输出、XML输出日志记录流程不变格式算法可替换计算引擎不同排序算法、不同量化策略、不同推荐策略数据分析不同的特征提取方式、不同的模型推理方式这些场景的共同点是算法家族内部差异大但对外接口足够统一。策略模式正好把差异封装起来让上层调用稳定。6.2 策略模式被滥用的三种迹象策略模式也不是万能的我见过不少用力过猛的项目策略类多到爆炸反而降低了可维护性。策略数量极少且永远不会增加如果将来只有一种实现策略模式纯属多此一举。策略需要对上下文进行反向操作策略算法需要访问上下文的大量内部数据甚至要调用上下文内部方法这时候强行用策略模式会导致策略和上下文高度耦合不如直接使用模板方法模式或干脆写成普通成员函数。算法选择逻辑极其复杂当客户端难以自行决定用哪个策略需要一个庞大的工厂来做决定时策略模式和工厂模式往往要配合使用。如果工厂内部又是几十行的if-else那只是把复杂分支从上下文搬到了工厂而已并没有真正消灭问题。6.3 与模板方法模式的辨析策略模式容易和模板方法模式搞混因为两者都涉及把算法的不同部分剥离开。区别在于策略模式侧重整个算法的替换上下文把算法整体委托给策略对象算法之间是完全平级、可互换的。模板方法模式侧重算法骨架固定、部分步骤可变基类用虚函数占位派生类只能重写某些步骤算法骨架由基类掌控。举一个生活化的例子做菜这件事策略模式是厨师自己决定做中餐还是西餐换一种菜系就换一个厨师模板方法模式是上菜顺序固定开胃菜-主菜-甜点但主菜具体做什么由各家餐厅自行决定。如果你的场景是整个算法要整体替换选策略模式如果只是局部步骤可以被定制算法流程本身就那样模板方法模式更贴切。6.4 策略模式与工厂模式的协作方式在实际业务中策略对象往往不应该由客户端直接new出来而是由一个工厂根据配置或参数决定创建哪个策略。我通常会把工厂做得非常简单// strategy_factory.h #pragma once #include memory #include stdexcept #include price_strategy.h #include normal_strategy.h #include member_strategy.h #include promotion_strategy.h enum class UserType { NORMAL, MEMBER, PROMOTION }; std::unique_ptrPriceStrategy createStrategy(UserType type, double basePrice) { switch (type) { case UserType::NORMAL: return std::make_uniqueNormalPriceStrategy(); case UserType::MEMBER: return std::make_uniqueMemberPriceStrategy(); case UserType::PROMOTION: return std::make_uniquePromotionPriceStrategy(300.0, 50.0); default: throw std::invalid_argument(unknown user type); } }注意这里我把到底用哪个策略和策略怎么算分离了工厂负责决策策略负责计算。这样上下文客户端都只需要依赖抽象接口和工厂新增策略时改动范围被限制在工厂和新建类业务调用方依然不需要变动。至于basePrice参数实际上不用传给工厂因为计价时上下文会把原始价格传给策略的calculate工厂只需要确定策略类型。7. 重构实践把旧的if-else链安全迁移到策略模式7.1 迁移的步骤与节奏如果你手上已经有一段跑得不错的if-else代码不建议一次性推倒重来那样风险太大。我会按下面几个步骤做渐进式重构先抽出接口定义一个和现有函数签名一致的抽象策略接口。如果原函数是double calculatePrice(UserType type, double basePrice)可以先定义PriceStrategy接口方法double calculate(double basePrice) const。把每个分支提取成独立策略类一把梭地建类不要着急删掉旧的if-else函数。在旧函数内用策略模式重写让旧函数变成工厂上下文的组合先让它内部创建策略并调用对外继续保持原来的签名这样调用方无感。跑通全部用例后再清理确定新实现和旧逻辑输出一致才删掉旧函数或把调用方切过来。这种接口先行、策略并行、最后切换的节奏能最大程度降低重构风险。我在那个模拟项目X里就是这样干的上线后没有出现一例价格计算不一致的问题。7.2 迁移时容易遗漏的边界条件空指针/空策略对象旧的if-else即使传入未知类型也有默认行为迁移后如果策略没设置就调用calculate需要有兜底策略最好在建上下文时注入一个默认策略而不是允许空策略到处飘。参数传递差异旧函数可能除了basePrice还需要其他上下文数据比如用户等级、订单数量、地区码。策略接口在迁移初期可以先把这些参数打包成一个上下文参数结构体等重构稳定后再逐步收窄。异常处理行为旧的if-else里可能对某些规则失败做了降级处理比如折扣计算抛异常后回退原价迁移到策略模式后要决定这种降级逻辑放在策略内部还是上下文内部。我的建议是放在上下文里统一处理让策略只关心自己的算法不掺和异常策略的兜底。7.3 迁移前后对比一段真实代码的进化迁移前核心计价函数是这样的double calculatePrice(UserType type, double basePrice, int userLevel) { if (type UserType::NORMAL) { return basePrice; } else if (type UserType::MEMBER) { double discount userLevel 3 ? 0.7 : 0.8; return basePrice * discount; } else if (type UserType::PROMOTION) { return basePrice 300 ? basePrice - 50 : basePrice; } return basePrice; }迁移后原来的分支逻辑分别被封装进MemberStrategy和PromotionStrategy上下文成为纯调度的对象。计价规则增加新用户立减30时我只加一个NewUserStrategy类所有既有代码不用动。这种收益不是设计得好看这种虚无的理由而是实打实地把改动范围从动核心函数缩小到了新增一个类每次需求变更的心理负担完全不同。8. 我在这套代码上实际踩过的坑与最终体会8.1 第一个坑忘了在接口里写虚析构我第一次写策略接口时图省事只写了纯虚函数calculate没写析构函数。代码编译通过、跑起来也没问题直到后来用valgrind查内存泄漏才发现问题所有通过基类指针删除的派生类对象析构函数都不被调用内存一点一点漏。从那时起但凡有多态的类析构函数必然是虚的这句话就被我写进了团队的代码评审清单里再也没漏掉过。8.2 第二个坑上下文把策略和业务混在一起有一段时间我写上下文特别喜欢顺手做一些额外工作比如在calculate里先查缓存、再打日志、最后调策略。看起来很方便但后来发现缓存和日志其实也是可以变化的部分。今天用数据库缓存明天想换成Redis缓存后天日志要改JSON格式这些变化全都堆在上下文里上下文又开始膨胀。后来我把这些横切关注点从上下文里挪了出去只保留它作为策略调度的职责。日志、缓存可以放到装饰器或者调用方里上下文保持小而专。8.3 第三个坑策略复用引发的线程问题某个版本里我把一个NormalPriceStrategy设为单例对象然后多个OrderContext共享这个策略指针。程序跑了一段时间后某次促销策略改了内部参数结果影响到了另外一些完全不应该受影响的订单。归根结底是共享可变状态惹的祸。从那次之后我养成了一个习惯策略对象尽量保持无状态或不可变状态。如果需要动态参数比如满减阈值来自配置要么在创建策略时一次性传入要么在策略内部加锁保护绝不在使用过程中偷偷改内部变量。策略模式的好处之一就是策略可以安全复用但前提是策略本身得是线程友好的。8.4 一点真实感受策略模式不是一个能提高性能的模式它甚至会因为多一层间接调用而带来轻微额外开销。它的价值在于两个更重要的维度降低代码改动成本提升代码可读性。当你看到一支团队可以周五提出新计价规则、周一就把新策略类加进系统并且完全不动现有代码时你才会理解这种抽象值回票价。如果你正在学习C设计模式策略模式是一个极其合适的手写入门项目。它不像抽象工厂那样类多到让人头晕也不像观察者模式那样牵扯复杂的回调注册机制它恰到好处地展示了面向接口编程的精髓。把文中的代码复制下来自己改一改、加一个自己的策略、编译运行一遍基本上你就彻底理解了这个模式。如果还想继续深入我建议你沿着这几个方向自行扩展一是把手里的策略模式案例改造成std::function版本体会两种风格的取舍二是加入策略工厂让客户端连创建哪个策略都不需要关心三是尝试把一个真实的if-else链迁移到策略模式体验一次重构前后的差异。这三件事做完策略模式在你手里就不再是教科书定义而是真正属于你的工具箱里的一件趁手工具。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑