开闭原则的核心是新增功能靠加代码而非改老代码;抽象类定义稳定骨架、多态实现行为替换,通过final方法封装流程、abstract方法隔离变化,配合接口可兼顾契约与复用。

开闭原则的核心是:新增功能靠加代码,而不是改老代码。抽象类和多态配合使用,能有效隔离变化——抽象类定义稳定骨架,多态让运行时自动选择具体行为。
用抽象类划出不变的结构边界
抽象类适合表达“有共同流程但细节各异”的场景。它把固定步骤封装为 final 方法,把可变部分声明为 abstract,强制子类实现。
- 比如日志模块:log() 是统一入口(final),内部调用 doLog()(abstract)——这个方法必须由 FileLogger、DbLogger 等子类各自实现
- 抽象类不暴露具体实现,也不允许被 new;它只告诉子类“你必须提供什么”,而不规定“你怎么提供”
- 若抽象类中混入了字段或非 final 的默认逻辑,后续扩展容易被旧状态干扰,应尽量精简
多态让具体实现可插拔
业务代码只依赖抽象类类型,不关心实际是谁干活。只要子类遵循抽象类契约,运行时就能无缝替换。
你接手一个需求时,是不是经常这样:"改一个小字段 → 结果把不相关的代码也改了"、"需求说得很模糊 → 做着做着发现跑偏了"、"数据库加个字段 → 上线才发现漏了数据兼容"、"做了个新功能 → 一个月后自己都忘了当时怎么设计的"、"写到一半被中断 → 再回来不知道从哪继续";这个 Skill 就是专门解决这些问题的。 它会自动探测你的项目环境(JDK、Spring Boot、ORM、数据库、前端框架),记在
- 例如 OrderService 持有 Logger logger(抽象类引用),调用 logger.log("order created") 即可
- 上线时注入 FileLogger,压测时换成 MockLogger,都不用动 OrderService 的一行代码
- 新增 SyslogLogger?只需继承 Logger、重写 doLog(),再在创建处切换实例(如工厂返回新类型)
避免常见陷阱:别让抽象变成负担
抽象类不是越多越好,关键看它是否真的承载了稳定共性。否则容易演变成“伪抽象”。
立即学习“Java免费学习笔记(深入)”;
- 不要在抽象类里写 if-else 分支判断类型——这等于把变化逻辑硬塞进稳定层
- 不要让子类之间互相依赖(如 A 子类里 new B 子类),否则加一个子类可能引发连锁修改
- 接口比抽象类更轻量,如果只是行为契约、无共享流程,优先选接口;抽象类更适合有模板方法的场景
和接口搭配用效果更好
抽象类管“怎么做”,接口管“做什么”。两者可以组合:定义接口描述能力(如 Loggable),抽象类实现通用逻辑(如带时间戳的日志前处理),子类专注差异点。
- 比如 Payment 接口声明 pay(),AbstractPayment 提供公共验签、幂等处理,WechatPay、AlipayPay 各自补全渠道特有逻辑
- 这样既满足 OCP(加新支付方式 = 新建子类 + 注册),又避免重复代码

















