公司动态

C++策略模式实战:构建可扩展支付系统,告别if-else噩梦

📅 2026/7/16 9:58:11
C++策略模式实战:构建可扩展支付系统,告别if-else噩梦
1. 项目概述为什么我们需要策略模式如果你写过一段时间的C尤其是处理过那些需要根据不同条件执行不同算法的业务逻辑你肯定遇到过这样的场景一个函数里塞满了if-else或者switch-case每增加一种新情况你就得去修改这个函数的内部代码。比如一个电商系统计算运费普通用户、VIP用户、海外用户各有不同的折扣算法。最开始你可能这么写double calculateShippingCost(UserType userType, double baseCost) { if (userType UserType::Normal) { return baseCost; } else if (userType UserType::VIP) { return baseCost * 0.9; } else if (userType UserType::Overseas) { return baseCost * 1.5; } // ... 未来再加新用户类型这里还得继续加 return baseCost; }这段代码的问题显而易见违反开闭原则。对扩展开放是做到了能加新的else if但对修改关闭却没做到——每次加新类型你都得动这个核心函数。一旦这个函数被多处调用或者逻辑更复杂维护就成了噩梦。策略模式要解决的就是这种“行为”或“算法”的灵活切换问题。它的核心思想是把算法家族单独抽取出来封装成一系列独立的类策略让它们之间可以互相替换。这样使用算法的上下文Context类就不再依赖具体的算法实现而是依赖一个抽象的算法接口。想换算法简单给上下文对象换一个策略对象就行了根本不用动上下文本身的代码。HeadFirst设计模式这本书之所以经典就是因为它用最生活化的例子比如模拟鸭子游戏讲透了这些模式的思想。今天我们就用C从零开始把策略模式掰开揉碎了讲清楚。我会带你写一个比经典“排序策略”更贴近实际项目的例子——一个支持多种支付方式的订单处理系统并分享我在实际项目中应用策略模式时踩过的坑和总结的技巧。2. 策略模式的核心思想与UML类图拆解2.1 模式定义与角色分工策略模式Strategy Pattern属于行为型设计模式。它的官方定义是定义一系列算法将每个算法封装起来并使它们可以互相替换。策略模式让算法的变化独立于使用算法的客户。听起来有点绕我们直接看它的核心角色通常有三个策略接口Strategy这是一个抽象类或者接口在C中通常用抽象类它声明了所有具体策略类必须实现的方法。这就是算法家族的“宪法”。具体策略ConcreteStrategy这些类实现了策略接口各自封装了具体的算法或行为。每一个具体策略类都提供了算法的一种实现。上下文Context这个类持有一个对策略对象的引用。上下文并不关心具体是哪个策略它只通过策略接口与策略对象交互。上下文通常会提供一个方法如setStrategy来允许客户端在运行时切换策略。2.2 UML类图与C映射用UML类图可以最直观地理解它们之间的关系---------------- --------------------- | Context | | Interface | |----------------| | Strategy | | -strategy: Strategy* |------|---------------------| |----------------| | execute(): void | | setStrategy(Strategy*)| --------------------- | contextInterface() | ^ ---------------- / \ / \ / \ --------------------- --------------------- | ConcreteStrategyA | | ConcreteStrategyB | |---------------------| |---------------------| | execute(): void | | execute(): void | --------------------- ---------------------在C中的具体映射Strategy通常是一个包含纯虚函数的抽象类。ConcreteStrategyA/B继承自Strategy抽象类并实现纯虚函数。Context包含一个Strategy*类型的成员指针现代C更推荐使用智能指针。它通过这个指针调用策略的方法。组合关系箭头带实心菱形表示Context“拥有”一个Strategy。关键点在于Context对ConcreteStrategy一无所知它只认识Strategy这个抽象接口。这就实现了依赖倒置——高层模块Context不依赖低层模块ConcreteStrategy二者都依赖于抽象Strategy。2.3 与相似模式的区别避免误用新手常常混淆策略模式和状态模式因为它们类图长得太像了。这里必须厘清策略模式客户端主动选择并指定使用哪种策略。策略之间是平等的、可选的目的是为了提供不同的算法实现。例如支付时用户手动选择“支付宝”或“微信支付”。状态模式状态之间的转换通常由上下文自身或状态对象内部逻辑决定客户端不直接干预。状态代表了对象内部状况的改变状态切换是自动的、有状态的。例如一个订单对象的状态从“待支付”自动流转到“已支付”再流转到“已发货”。简单记策略是“你做这件事用什么方法”状态是“你现在处于什么情况”。3. 实战构建一个可扩展的支付处理系统光说不练假把式。我们用一个电商场景下的订单支付系统作为例子。系统需要支持支付宝、微信支付、信用卡支付并且未来可能轻松接入银联、数字货币等新支付方式。3.1 定义策略接口与具体策略首先定义我们的策略接口IPaymentStrategy。它只关心一件事执行支付。// payment_strategy.h #ifndef PAYMENT_STRATEGY_H #define PAYMENT_STRATEGY_H #include string #include memory // 策略接口支付策略 class IPaymentStrategy { public: virtual ~IPaymentStrategy() default; // 虚析构函数确保正确释放派生类资源 // 执行支付操作 // param amount: 支付金额 // return: 支付结果信息 virtual std::string executePayment(double amount) 0; }; #endif // PAYMENT_STRATEGY_H注意策略接口的析构函数一定要是virtual的。这是C多态的基础如果基类指针指向派生类对象非虚析构会导致派生类的析构函数不被调用可能引发资源泄漏。 default是C11后的好习惯让编译器生成默认实现。接下来实现几个具体的支付策略。每个策略类都独立封装了一种支付方式的全部逻辑。// concrete_strategies.h #ifndef CONCRETE_STRATEGIES_H #define CONCRETE_STRATEGIES_H #include payment_strategy.h #include iostream #include sstream #include iomanip // 具体策略支付宝支付 class AlipayStrategy : public IPaymentStrategy { public: std::string executePayment(double amount) override { // 模拟调用支付宝SDK的复杂逻辑 std::ostringstream oss; oss std::fixed std::setprecision(2); oss [支付宝支付] 成功支付 ¥ amount; oss 。订单已通过蚂蚁金服通道结算预计1秒内到账。; // 这里可以集成真实的支付宝API调用、参数签名、回调处理等 return oss.str(); } }; // 具体策略微信支付 class WechatPayStrategy : public IPaymentStrategy { public: std::string executePayment(double amount) override { std::ostringstream oss; oss std::fixed std::setprecision(2); oss [微信支付] 成功支付 ¥ amount; oss 。微信担保交易已完成资金已进入商户平台。; // 这里可以集成微信支付统一下单、支付结果通知等逻辑 return oss.str(); } }; // 具体策略信用卡支付假设需要卡号信息 class CreditCardStrategy : public IPaymentStrategy { private: std::string cardNumber; // 策略可以有自己的状态 std::string expiryDate; public: CreditCardStrategy(const std::string cardNum, const std::string expiry) : cardNumber(cardNum), expiryDate(expiry) { // 在实际项目中这里应该对卡号进行脱敏处理而不是直接存储明文 } std::string executePayment(double amount) override { std::ostringstream oss; oss std::fixed std::setprecision(2); // 显示卡号后四位 std::string maskedCard ****-****-****- cardNumber.substr(cardNumber.length() - 4); oss [信用卡支付] 使用卡号 maskedCard 成功支付 ¥ amount; oss 。发卡行已授权交易流水号: generateTransactionId(); // 这里可以集成支付网关、3D认证等复杂流程 return oss.str(); } private: std::string generateTransactionId() const { // 模拟生成交易流水号 return CC_TX_ std::to_string(static_castlong(time(nullptr))); } }; #endif // CONCRETE_STRATEGIES_H实操心得1策略类的状态管理上面的CreditCardStrategy展示了策略对象可以拥有自己的内部状态卡号、有效期。这很有用但要注意轻量状态策略对象最好是无状态或轻量状态的。如果状态很重或者创建成本高需要考虑使用享元模式来共享策略实例。线程安全如果策略对象有状态并且在多线程环境下被多个Context共享你必须确保其线程安全性。通常更简单的做法是每个Context持有自己的策略实例避免共享。3.2 构建上下文订单处理类现在创建我们的上下文类PaymentContext它代表一个待支付的订单。// payment_context.h #ifndef PAYMENT_CONTEXT_H #define PAYMENT_CONTEXT_H #include payment_strategy.h #include memory #include string class PaymentContext { private: std::unique_ptrIPaymentStrategy paymentStrategy; // 持有策略的智能指针 std::string orderId; double amount; public: // 构造函数创建订单时不一定需要策略可以后续设置 PaymentContext(const std::string id, double amt) : orderId(id), amount(amt), paymentStrategy(nullptr) {} // 设置支付策略运行时动态切换 void setPaymentStrategy(std::unique_ptrIPaymentStrategy strategy) { paymentStrategy std::move(strategy); std::cout 订单 orderId 的支付策略已更新。 std::endl; } // 执行支付操作上下文将实际工作委托给策略对象 void processPayment() { if (!paymentStrategy) { std::cerr 错误订单 orderId 未设置支付策略无法完成支付。 std::endl; return; } std::cout 开始处理订单 orderId 支付金额: ¥ amount std::endl; std::string result paymentStrategy-executePayment(amount); std::cout result std::endl; std::cout 订单 orderId 支付流程结束。 std::endl; // 这里可以后续添加更新订单状态、发送通知等通用逻辑 } // 获取订单信息可选 std::string getOrderInfo() const { return 订单ID: orderId , 金额: ¥ std::to_string(amount); } }; #endif // PAYMENT_CONTEXT_H关键设计解析为什么用std::unique_ptr明确所有权unique_ptr明确表示PaymentContext独占这个策略对象的所有权。当PaymentContext对象销毁时策略对象也会自动销毁防止内存泄漏。支持运行时替换setPaymentStrategy方法使用std::move来转移所有权这非常高效并且清晰地表达了“旧的策略被丢弃新的策略被接管”的语义。避免切片问题使用指针或智能指针是实现多态的必要条件。如果直接使用IPaymentStrategy类型的成员变量会发生对象切片丢失派生类的信息。3.3 客户端代码与运行演示最后看看客户端如何灵活地使用这个系统。// main.cpp #include payment_context.h #include concrete_strategies.h #include iostream #include vector int main() { std::cout 电商平台支付系统演示 \n std::endl; // 1. 创建几个订单 PaymentContext order1(ORDER_20231027_001, 299.99); PaymentContext order2(ORDER_20231027_002, 1500.50); PaymentContext order3(ORDER_20231027_003, 88.88); // 2. 为订单设置不同的支付策略 std::cout \n--- 场景1用户选择不同支付方式 --- std::endl; order1.setPaymentStrategy(std::make_uniqueAlipayStrategy()); order1.processPayment(); order2.setPaymentStrategy(std::make_uniqueWechatPayStrategy()); order2.processPayment(); // 信用卡支付需要初始化信息 order3.setPaymentStrategy(std::make_uniqueCreditCardStrategy(1234567812345678, 12/25)); order3.processPayment(); // 3. 演示运行时动态切换策略例如用户重试支付 std::cout \n--- 场景2运行时动态切换策略 --- std::endl; PaymentContext retryOrder(ORDER_RETRY_001, 199.0); retryOrder.setPaymentStrategy(std::make_uniqueCreditCardStrategy(8765432187654321, 06/24)); retryOrder.processPayment(); std::cout \n 模拟信用卡支付失败用户切换为支付宝支付 std::endl; retryOrder.setPaymentStrategy(std::make_uniqueAlipayStrategy()); // 动态切换 retryOrder.processPayment(); // 4. 结合工厂模式进阶用法 std::cout \n--- 场景3结合简单工厂创建策略 --- std::endl; auto createStrategy [](const std::string type) - std::unique_ptrIPaymentStrategy { if (type alipay) return std::make_uniqueAlipayStrategy(); if (type wechat) return std::make_uniqueWechatPayStrategy(); if (type creditcard) return std::make_uniqueCreditCardStrategy(default_card, 01/30); return nullptr; // 或者抛出异常 }; PaymentContext factoryOrder(ORDER_FACTORY_001, 450.75); auto strategy createStrategy(wechat); // 从配置或数据库读取 if (strategy) { factoryOrder.setPaymentStrategy(std::move(strategy)); factoryOrder.processPayment(); } std::cout \n 演示结束 std::endl; return 0; }编译并运行假设使用gg -stdc14 -o payment_system main.cpp payment_context.h concrete_strategies.h payment_strategy.h ./payment_system你会看到清晰的输出展示了不同策略的执行以及运行时策略切换的过程。这个系统的最大优点是当我们需要增加一个新的支付方式比如“数字货币支付”时只需要做两件事创建一个新的类CryptoCurrencyStrategy继承IPaymentStrategy并实现executePayment。在客户端代码或工厂方法中能够创建这个新类的实例。我们完全不需要修改PaymentContext、IPaymentStrategy接口或者其他任何已有的支付策略类。这完美符合了开闭原则。4. 深入探讨C实现策略模式的高级技巧与陷阱4.1 策略对象的创建与管理策略对象由谁创建何时销毁是实际项目中的关键决策点。客户端创建如上例最简单直接但客户端需要知道所有具体策略类增加了耦合。适用于策略种类固定且不多的场景。简单工厂模式如上例中的createStrategy函数。客户端只需传入一个标识符如字符串由工厂负责创建对应的策略对象。这隐藏了具体类的创建细节。依赖注入在大型框架中如Spring for C策略对象常通过依赖注入容器来管理生命周期和依赖关系。上下文类通过构造函数或Setter接收注入的策略对象。单例策略如果策略类是无状态的例如只包含纯算法函数可以将其实现为单例避免重复创建开销。但要注意线程安全。// 无状态策略的单例实现示例 class DiscountStrategy : public IPaymentStrategy { private: DiscountStrategy() default; // 私有构造函数 public: static DiscountStrategy getInstance() { static DiscountStrategy instance; // C11保证静态局部变量线程安全 return instance; } // 删除拷贝构造和赋值 DiscountStrategy(const DiscountStrategy) delete; DiscountStrategy operator(const DiscountStrategy) delete; std::string executePayment(double amount) override { // ... 实现折扣计算 return Discount applied; } }; // 使用context.setPaymentStrategy(DiscountStrategy::getInstance()); // 注意这里需要处理指针所有权可能需用引用或自定义deleter4.2 使用std::function与Lambda的轻量级策略对于非常简单的、一次性使用的策略行为C11的std::function和Lambda表达式提供了更灵活的替代方案无需定义一堆小类。// 使用std::function作为策略的上下文 class SimpleCalculator { private: std::functiondouble(double, double) operation; // 策略就是一个可调用对象 public: void setStrategy(std::functiondouble(double, double) op) { operation std::move(op); } double calculate(double a, double b) { if (operation) { return operation(a, b); } throw std::runtime_error(No strategy set); } }; int main() { SimpleCalculator calc; // 策略1加法 (Lambda) calc.setStrategy([](double a, double b) { return a b; }); std::cout Add: calc.calculate(5, 3) std::endl; // 策略2乘法 (函数指针) calc.setStrategy(std::multipliesdouble()); // 使用标准库函数对象 std::cout Multiply: calc.calculate(5, 3) std::endl; // 策略3复杂逻辑 auto customOp [](double a, double b) { return (a * a) (b * b); }; calc.setStrategy(customOp); std::cout Custom: calc.calculate(3, 4) std::endl; return 0; }何时用std::function何时用传统接口用std::function策略逻辑非常简单几行代码且不需要复杂的状态管理或继承体系。代码更简洁。用传统接口策略行为复杂需要多个方法策略之间有共享的代码可以放在基类策略需要被放入容器统一管理如vectorIPaymentStrategy*或者你需要清晰的类型层次和文档。4.3 性能考量与优化虚函数开销通过基类指针调用虚函数strategy-executePayment()会有一次间接寻址vptr查找的开销。对于性能极其敏感的实时系统如高频交易这可能成为瓶颈。对策如果策略类型在编译期可知可以考虑使用策略模式与模板的结合Policy-Based Design利用C模板的静态多态来消除运行时开销。template typename PaymentPolicy class OrderProcessor { PaymentPolicy payment; public: void checkout(double amount) { payment.pay(amount); // 编译期绑定无虚函数开销 } }; // 使用时OrderProcessorAlipayPolicy order; // 策略在编译期确定缺点是失去了运行时的动态切换能力。对象创建开销频繁创建和销毁策略对象如在循环中可能影响性能。对策使用对象池复用策略对象或者确保策略对象轻量。对于无状态策略使用单例或静态方法。内存对齐与缓存如果策略对象大小不一在容器中存储指针可能导致内存访问效率降低。通常这不是主要矛盾但在大规模数据处理时需要留意。4.4 策略模式与模板方法模式的关系这两个模式都用于封装算法但方向不同模板方法模式在父类中定义算法的骨架将一些步骤延迟到子类中实现。子类不能改变算法结构只能重写特定步骤。是继承关系。策略模式将整个算法完全封装在独立的策略类中通过组合的方式让客户端选择。可以彻底替换算法。可以这样理解模板方法是“填空题”父类出题子类填空策略模式是“选择题”客户端从多个完整答案中选一个。5. 常见问题、调试技巧与最佳实践5.1 典型问题排查表问题现象可能原因解决方案程序崩溃报Segmentation fault上下文持有的策略指针是野指针或已释放。1. 使用智能指针std::unique_ptr/std::shared_ptr自动管理生命周期。2. 检查策略对象是否在栈上创建后地址被传递而原对象已销毁。策略切换后旧策略的逻辑仍在生效策略对象有内部状态且新旧策略对象被意外共享。1. 确保setStrategy时旧策略被正确替换或销毁。2. 如果策略有状态考虑深拷贝或每次创建新实例。编译错误cannot declare field to be of abstract type在上下文类中试图直接实例化抽象策略类。上下文类应持有策略类的指针或引用而不是具体对象。确保成员变量类型是IPaymentStrategy*或std::unique_ptrIPaymentStrategy。运行时多态失效总是调用基类函数策略基类的接口函数没有声明为virtual或者派生类函数签名不匹配如const修饰符。1. 确认基类函数是virtual。2. 在派生类中使用override关键字让编译器检查签名是否匹配。内存泄漏使用了原始指针(Strategy*)但忘记delete。强烈推荐使用智能指针。如果必须用原始指针明确所有权并在上下文析构函数中delete。5.2 最佳实践与心得优先使用组合而非继承策略模式本身就是“组合”的典范。它比通过继承在子类中重写算法要灵活得多避免了类爆炸为每一种算法组合创建一个子类和脆弱的基类问题。使策略接口保持精简接口应该只定义算法必须的方法。如果接口过于庞大具体策略类将被迫实现许多不需要的方法。可以考虑拆分成更小的接口接口隔离原则。考虑策略的无状态化尽可能设计无状态的策略类。无状态的策略对象可以被安全地共享和复用甚至实现为单例减少对象创建开销。与工厂模式搭档当具体策略类较多或创建逻辑复杂时使用工厂模式来创建策略对象可以进一步解耦客户端与具体策略类。不要过度设计如果只有一两个简单的、几乎不会变化的算法直接使用if-else或函数指针可能更简单清晰。引入策略模式需要额外的抽象层在简单场景下可能是杀鸡用牛刀。为策略命名给具体策略类起一个能清晰表达其算法意图的名字比如FirstComeFirstServedScheduler而不是SchedulerA。这能极大提升代码的可读性。5.3 测试策略模式对使用策略模式的代码进行单元测试会非常愉快因为每个策略都可以独立测试。// 使用Google Test框架的示例 TEST(PaymentStrategyTest, AlipayStrategyPaysCorrectAmount) { AlipayStrategy strategy; testing::internal::CaptureStdout(); // 捕获输出 std::string result strategy.executePayment(100.0); std::string output testing::internal::GetCapturedStdout(); // 验证结果字符串包含关键信息 EXPECT_THAT(result, testing::HasSubstr(支付宝支付)); EXPECT_THAT(result, testing::HasSubstr(100.00)); } TEST(PaymentContextTest, CanProcessPaymentWithMockStrategy) { // 使用模拟策略进行测试 class MockStrategy : public IPaymentStrategy { public: MOCK_METHOD(std::string, executePayment, (double amount), (override)); }; testing::NiceMockMockStrategy mockStrategy; PaymentContext context(TEST_ORDER, 50.0); context.setPaymentStrategy(std::make_uniqueMockStrategy(mockStrategy)); // 注意这里需要处理unique_ptr和mock对象的结合实际中可能用shared_ptr或原始指针 // 设置期望 EXPECT_CALL(mockStrategy, executePayment(50.0)) .WillOnce(testing::Return(Mock Payment Successful)); // 执行 testing::internal::CaptureStdout(); context.processPayment(); std::string output testing::internal::GetCapturedStdout(); EXPECT_THAT(output, testing::HasSubstr(Mock Payment Successful)); }通过策略模式我们将易于变化的算法部分隔离出来使得核心业务逻辑PaymentContext保持稳定。当支付渠道增加、费率调整、促销规则变化时我们只需要新增或修改对应的策略类系统的其他部分就像什么都没发生一样继续工作。这种通过“分离变化”来提升代码可维护性和扩展性的能力正是设计模式的精髓所在。