开闭原则(OCP)下:支付系统的策略模式实现

上一讲我们聊了开闭原则的理论,说白了就是「对扩展开放,对修改关闭」。今天咱们来点实在的——拿一个支付系统开刀,看看策略模式怎么帮我们落地OCP。

我在好几个项目里都做过支付模块。说实话,支付这玩意儿变化特别快。今天接微信,明天接支付宝,后天又来个银联云闪付。要是每次加一种支付方式都要改核心代码,那真是噩梦。

一个糟糕的支付设计

先看个反面教材。我见过不少新手这么写:

class PaymentProcessor {
public:
    void process(const std::string& type, double amount) {
        if (type == "alipay") {
            // 支付宝逻辑
            std::cout << "支付宝支付: " << amount << std::endl;
        } else if (type == "wechat") {
            // 微信支付逻辑
            std::cout << "微信支付: " << amount << std::endl;
        } else if (type == "unionpay") {
            // 银联支付逻辑
            std::cout << "银联支付: " << amount << std::endl;
        }
        // 再加一种?继续加else if...
    }
};

这段代码有什么问题?

  • 每次新增支付方式,都要修改process方法
  • 所有支付逻辑耦合在一起,测试困难
  • 违反了开闭原则——对修改是开放的

我曾经在一个老项目里接手过类似的代码,光支付方式就有十几种,那个if-else链长得能绕地球半圈。改一个地方,生怕影响其他支付方式。嗯,那滋味不好受。

策略模式来救场

策略模式的核心思想很简单:把每个算法封装成独立的策略类,让它们可以互相替换。这样,新增策略就不需要修改现有代码了。

看下怎么用策略模式重构上面的支付系统:

// 支付策略接口
class PaymentStrategy {
public:
    virtual ~PaymentStrategy() = default;
    virtual void pay(double amount) = 0;
};

// 支付宝策略
class AlipayStrategy : public PaymentStrategy {
public:
    void pay(double amount) override {
        std::cout << "支付宝支付: " << amount << std::endl;
        // 支付宝特有的签名、回调等逻辑
    }
};

// 微信支付策略
class WechatPayStrategy : public PaymentStrategy {
public:
    void pay(double amount) override {
        std::cout << "微信支付: " << amount << std::endl;
        // 微信特有的逻辑
    }
};

// 银联支付策略
class UnionPayStrategy : public PaymentStrategy {
public:
    void pay(double amount) override {
        std::cout << "银联支付: " << amount << std::endl;
        // 银联特有的逻辑
    }
};

// 上下文类——负责使用策略
class PaymentContext {
private:
    std::unique_ptr<PaymentStrategy> strategy_;
public:
    void setStrategy(std::unique_ptr<PaymentStrategy> strategy) {
        strategy_ = std::move(strategy);
    }
    
    void executePayment(double amount) {
        if (!strategy_) {
            throw std::runtime_error("未设置支付策略");
        }
        strategy_->pay(amount);
    }
};

现在,新增一种支付方式只需要写一个新类,继承PaymentStrategy。核心的PaymentContext一行代码都不用改。这就是开闭原则的体现——对扩展开放,对修改关闭。

关键点:策略模式把「变化」封装在独立的类中,通过组合的方式让上下文使用策略。这样变化被隔离了,核心逻辑稳定了。

OCP与继承、组合的关系

说到这儿,我想聊聊OCP和继承、组合的关系。很多人以为OCP就是多用继承,其实不然。

继承的问题:

  • 继承是静态的,编译时就确定了
  • 子类会继承父类的所有接口和实现,容易造成接口污染
  • 深度继承链难以维护

组合的优势:

  • 组合是动态的,运行时可以替换
  • 组合只依赖接口,不依赖具体实现
  • 组合更灵活,符合「优先使用组合而非继承」的原则

你看上面的策略模式,PaymentContext并没有继承PaymentStrategy,而是通过组合的方式持有策略对象。这样,运行时可以随时切换策略,完全不需要修改PaymentContext的代码。

我的建议:能用组合解决的问题,尽量别用继承。继承适合「is-a」关系,组合适合「has-a」关系。支付系统里,上下文「拥有」一个策略,而不是「是」一个策略。

支付系统的策略模式结构图

下面这张图展示了策略模式在支付系统中的整体结构:

支付系统策略模式结构图 PaymentContext setStrategy() / executePayment() 组合关系 <<接口>> PaymentStrategy + pay(amount) AlipayStrategy 支付宝特有逻辑 WechatPayStrategy 微信特有逻辑 UnionPayStrategy 银联特有逻辑 新增策略 → 只需添加新类

从图上可以清楚看到:PaymentContext只依赖PaymentStrategy接口,不依赖具体实现。新增一种支付方式,只需要在右侧添加一个新的策略类,其他代码纹丝不动。

实际项目中的避坑指南

策略模式虽好,但用不好也会翻车。我分享几个实际踩过的坑:

我曾经犯过的错:

  • 策略对象创建耦合:一开始我在PaymentContext里用if-else创建策略对象,结果每次新增策略还是要改上下文。后来改用工厂模式或依赖注入来解决。
  • 策略接口设计过粗:所有策略共用一个接口,但有些策略需要额外的参数(比如支付宝需要appId,微信需要商户号)。我建议用参数对象封装,或者用建造者模式传递配置。
  • 忘记处理空策略:如果没有设置策略就调用executePayment,程序会崩溃。记得加空指针检查,或者提供一个默认策略。

策略模式的适用场景

不是所有地方都需要策略模式。我个人习惯在以下场景使用:

场景 是否适合策略模式 说明
多种支付方式 ✅ 非常适合 每种支付方式独立封装,互不干扰
多种排序算法 ✅ 非常适合 运行时动态选择排序策略
多种文件格式解析 ✅ 适合 每种格式一个解析器
单一算法但有少量分支 ❌ 不适合 用简单if-else或switch就够了
策略数量极少且不会变化 ❌ 不适合 过度设计,增加复杂度

小技巧:如果你不确定要不要用策略模式,先写简单的if-else。等真的需要新增策略时,再重构也不迟。别一开始就过度设计。

总结

开闭原则和策略模式是天生一对。策略模式通过组合的方式,把变化封装在独立的策略类中,让核心代码对修改关闭,对扩展开放。

记住三个要点:

  • 面向接口编程,不要面向实现编程
  • 优先使用组合,而不是继承
  • 把变化封装起来,让稳定部分不受影响

你想想看,如果每个模块都遵循这个思路,代码会变得多好维护?我在项目里推行这种设计后,新增支付方式从原来的改半天变成写一个类几分钟搞定。嗯,这就是开闭原则的魅力。

专注资料整理