开闭原则:对扩展开放、对修改关闭

开闭原则,英文叫 Open-Closed Principle,简称 OCP。它是面向对象设计的核心原则之一,也是我这些年写代码最看重的一条。

说白了,它的意思就是:一个软件实体(类、模块、函数)应该对扩展开放,对修改关闭。

嗯,这句话听起来有点绕。我换个说法:你写好的代码,尽量不要去改它。但如果要加新功能,你得能轻松地加进去。

为什么这么重要?我在项目中遇到过太多次了——一个类被改了又改,最后变得臃肿不堪,谁都不敢动。你想想看,每次加需求都要改老代码,改着改着就出 bug 了。

违反开闭原则的典型例子

先看一个反面教材。假设我们要做一个支付系统,一开始只支持微信支付:

public class PaymentService {
    public void pay(String type, double amount) {
        if ("wechat".equals(type)) {
            System.out.println("微信支付:" + amount);
        }
    }
}

后来要加支付宝,你怎么办?只能改这个类:

public class PaymentService {
    public void pay(String type, double amount) {
        if ("wechat".equals(type)) {
            System.out.println("微信支付:" + amount);
        } else if ("alipay".equals(type)) {
            System.out.println("支付宝支付:" + amount);
        }
    }
}

再后来要加银行卡、信用卡、Apple Pay……这个类会变成什么样子?

⚠️ 我曾经见过一个支付类,里面写了十几个 if-else,足足 800 多行。 每次加新支付方式,都要在这个类里加一段逻辑。测试要回归所有支付方式,因为改的是同一个方法。这就是典型的违反开闭原则。

如何遵守开闭原则?

核心思路就是:抽象 + 多态。把变化的部分抽象成接口或抽象类,然后让具体的实现类去扩展。

我个人的习惯是:先定义好抽象层,再写具体实现。这样后续加功能,只需要新增一个类,不用动老代码。

实战:策略模式实现支付系统

策略模式,说白了就是把「算法」或「行为」封装起来,让它们可以互相替换。这正好符合开闭原则——对扩展开放,对修改关闭。

第一步:定义支付策略接口

public interface PaymentStrategy {
    void pay(double amount);
}

第二步:实现具体策略

public class WechatPay implements PaymentStrategy {
    @Override
    public void pay(double amount) {
        System.out.println("微信支付:" + amount + " 元");
    }
}

public class Alipay implements PaymentStrategy {
    @Override
    public void pay(double amount) {
        System.out.println("支付宝支付:" + amount + " 元");
    }
}

public class BankCardPay implements PaymentStrategy {
    @Override
    public void pay(double amount) {
        System.out.println("银行卡支付:" + amount + " 元");
    }
}

第三步:上下文类

public class PaymentContext {
    private PaymentStrategy strategy;

    public PaymentContext(PaymentStrategy strategy) {
        this.strategy = strategy;
    }

    public void setStrategy(PaymentStrategy strategy) {
        this.strategy = strategy;
    }

    public void executePay(double amount) {
        strategy.pay(amount);
    }
}

第四步:客户端使用

public class Client {
    public static void main(String[] args) {
        PaymentContext context = new PaymentContext(new WechatPay());
        context.executePay(100.0);

        context.setStrategy(new Alipay());
        context.executePay(200.0);

        context.setStrategy(new BankCardPay());
        context.executePay(300.0);
    }
}
🎯 关键点: 如果要加新的支付方式,比如 Apple Pay,只需要新增一个类实现 PaymentStrategy 接口,完全不用改 PaymentContext 和已有的支付类。这就是对扩展开放、对修改关闭。

策略模式的核心结构图

下面这张图,是我画的一个策略模式的结构图,帮你理清各个角色之间的关系:

策略模式结构图 PaymentContext - strategy: PaymentStrategy + executePay(amount) <<interface>> PaymentStrategy + pay(amount) WechatPay + pay(amount) Alipay + pay(amount) BankCardPay + pay(amount) Context 持有策略接口,具体策略实现接口,运行时可以自由切换

策略模式的优势

  • 符合开闭原则:新增支付方式不需要改现有代码
  • 消除条件判断:不用写一堆 if-else 或 switch
  • 提高可测试性:每个策略可以单独测试
  • 运行时切换:可以在程序运行过程中动态改变行为
💡 避坑指南: 我曾经在一个项目中,把策略模式用得太过了——每个支付方式都单独一个类,结果支付方式有 30 多种,类文件一大堆。后来我调整了一下:对于逻辑非常简单的支付方式,可以用枚举或函数式接口来简化。策略模式虽好,但不要滥用。

什么时候用策略模式?

场景 说明
多种支付方式 微信、支付宝、银行卡、信用卡等
多种促销活动 满减、折扣、赠品等
多种排序算法 按价格、按销量、按评价等
多种导出格式 PDF、Excel、CSV 等

总结

开闭原则,说白了就是:别改老代码,加新代码。策略模式是实现开闭原则的经典手段。把变化的部分抽象成接口,让具体实现去扩展。这样你的代码会越来越灵活,越来越稳定。

我个人的经验是:写代码之前,先想想哪些地方可能会变。把这些变化点抽象出来,后面你会感谢自己的。

📌 记住: 好的设计不是一次写出来的,而是为未来的变化留好了空间。开闭原则 + 策略模式,就是帮你留好空间的那把钥匙。

公众号:蓝海资料掘金营,微信deep3321