开闭原则要求对扩展开放、对修改关闭,即新增功能不修改原有已验证逻辑;应通过接口/抽象类+多态隔离变化点,避免if-else堆砌、硬编码实现类或伪开闭(如反射、泛型滥用)。

开闭原则不是让类“不改代码”,而是让扩展不破坏原有逻辑
开闭原则(OCP)常被误解为“禁止修改”,实际是要求对**扩展开放、对修改关闭**——即新增功能时,不该动已验证过的旧逻辑。Java 中最直接的落地方式,是把变化点抽成接口或抽象类,用多态隔离变与不变。
常见错误现象:if-else 或 switch 随着业务增多不断堆砌分支,每次加一种新类型都要改原方法;或者硬编码具体实现类,导致测试难、替换难、耦合紧。
- 使用场景:支付方式(支付宝/微信/Apple Pay)、通知渠道(短信/邮件/站内信)、策略计算(折扣规则、路由算法)
- 关键判断点:当发现某个方法里反复出现“如果 type == X 就做 Y”时,就是开闭原则被破坏的信号
- 参数差异:
PaymentService.process(PaymentType type)是坏味道;PaymentProcessor.process()(由子类各自实现)才是符合 OCP 的设计
用接口 + 工厂 + 依赖注入避免 new 具体实现
很多人写了接口和多个实现类,却在业务代码里直接 new WechatPay(),等于白搭——扩展时仍要改调用方。
实操建议:
立即学习“Java免费学习笔记(深入)”;
- 定义统一接口,如
NotificationSender,所有实现类(SmsSender、EmailSender)都实现它 - 用工厂类或 Spring 的
@Bean方法封装创建逻辑,避免散落各处的new - 业务类通过构造函数接收
NotificationSender,而不是自己决定用哪个实现 - 新增一种发送方式?只加一个实现类 + 注册到容器,其他代码零改动
性能影响几乎为零;兼容性上注意接口方法签名稳定,别轻易加默认方法(除非确定所有实现都能安全继承)。
警惕“伪开闭”:泛型擦除、反射、配置驱动带来的隐性修改成本
有人用 Class.forName(config.get("sender")) 加载实现类,以为这就开闭了。其实不然——类名写死在配置里,换包路径或改类名时,配置也得同步改,本质还是修改点转移了。
另一个坑是滥用泛型抽象,比如写个 BaseHandler<t extends request></t>,结果每个子类都要重写模板方法里的核心逻辑,反而更难维护。
- 反射加载类:容易抛
ClassNotFoundException或NoClassDefFoundError,且 IDE 无法静态检查调用链 - 过度抽象:当只有 2 个实现类且短期内不会增加时,硬套接口反而增加认知负担
- 配置驱动扩展:必须确保配置项命名规范、有文档、有校验(比如启动时检查类是否可实例化)
Spring Boot 下最轻量的 OCP 实践:@ConditionalOnProperty + 接口多实现
不用写工厂、不用配 XML,靠 Spring 自动装配就能做到“加类即生效”。前提是接口只有一个,实现类用不同条件激活。
示例结构:
public interface ReportGenerator {
String generate();
}
@Component
@ConditionalOnProperty(name = "report.type", havingValue = "pdf")
public class PdfReportGenerator implements ReportGenerator { ... }
@Component
@ConditionalOnProperty(name = "report.type", havingValue = "csv")
public class CsvReportGenerator implements ReportGenerator { ... }
只要配置 report.type=csv,Spring 就只注入 CsvReportGenerator。新增 JsonReportGenerator?加类、加注解、改配置——三步,不碰原有类。
容易忽略的是:多个实现类共存时,@Primary 或 @Qualifier 必须明确,否则启动报 NoUniqueBeanDefinitionException;还有,@ConditionalOnProperty 默认不校验属性是否存在,建议加 matchIfMissing = false 防误配。










