开闭原则:对扩展开放、对修改关闭
开闭原则,英文叫 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 和已有的支付类。这就是对扩展开放、对修改关闭。
策略模式的核心结构图
下面这张图,是我画的一个策略模式的结构图,帮你理清各个角色之间的关系:
策略模式的优势
- 符合开闭原则:新增支付方式不需要改现有代码
- 消除条件判断:不用写一堆 if-else 或 switch
- 提高可测试性:每个策略可以单独测试
- 运行时切换:可以在程序运行过程中动态改变行为
💡 避坑指南: 我曾经在一个项目中,把策略模式用得太过了——每个支付方式都单独一个类,结果支付方式有 30 多种,类文件一大堆。后来我调整了一下:对于逻辑非常简单的支付方式,可以用枚举或函数式接口来简化。策略模式虽好,但不要滥用。
什么时候用策略模式?
| 场景 | 说明 |
|---|---|
| 多种支付方式 | 微信、支付宝、银行卡、信用卡等 |
| 多种促销活动 | 满减、折扣、赠品等 |
| 多种排序算法 | 按价格、按销量、按评价等 |
| 多种导出格式 | PDF、Excel、CSV 等 |
总结
开闭原则,说白了就是:别改老代码,加新代码。策略模式是实现开闭原则的经典手段。把变化的部分抽象成接口,让具体实现去扩展。这样你的代码会越来越灵活,越来越稳定。
我个人的经验是:写代码之前,先想想哪些地方可能会变。把这些变化点抽象出来,后面你会感谢自己的。
📌 记住: 好的设计不是一次写出来的,而是为未来的变化留好了空间。开闭原则 + 策略模式,就是帮你留好空间的那把钥匙。
公众号:蓝海资料掘金营,微信deep3321