开闭原则要求抽象精准对应可预期变化:需共享默认逻辑时选抽象类,仅定义契约时优先接口;抽象粒度适中,依赖多态而非类型判断,继承中父类应“瘦”——只含模板、抽象方法和可选钩子。

开闭原则在 Java 中指导抽象类与接口设计的核心,是把“变”和“不变”分开:用抽象层固化稳定契约,让具体实现承担变化。它不鼓励随意抽象,而是要求抽象必须精准对应可预期的变化点。
抽象类与接口选哪个,取决于变化是否需要共享默认逻辑
如果多个子类有共用的流程骨架或基础行为(比如日志前/后处理、数据校验模板),用抽象类更合适——它能提供部分实现,同时留出 abstract 方法作为扩展点。
如果只是定义行为契约,且不同实现之间完全独立(如支付方式、图形绘制),优先用接口——它更轻量,支持多实现,也更利于解耦调用方。
例如:
- 定义
Logger抽象类,含log()模板方法和空的beforeLog()钩子,子类只重写doLog()即可; - 定义
Payment接口,微信、支付宝、Apple Pay 各自实现pay(),互不影响,新增支付方式也不动旧代码。
抽象粒度要适中,避免过早或过度抽象
抽象不是越多越好。一个接口只应封装一类相关变化,比如 Shape 只管面积计算,不该混入“渲染颜色”或“导出格式”逻辑。
常见反例:
- 把所有未来可能的支付字段(手续费、币种、回调地址)全塞进接口,导致每次加字段都要改接口——违反对修改关闭;
- 为还没出现的“区块链支付”提前设计泛型接口,结果没人用,还增加理解成本。
正确做法是:基于当前已知需求提炼最小稳定契约,等新场景出现时,再通过新增接口或继承层次来扩展。
抽象必须配合多态使用,否则形同虚设
定义了 Shape 接口,但如果业务代码里还用 if (shape instanceof Circle) 做类型判断,就等于绕过了抽象——此时新增三角形仍需改判断逻辑,OCP 失效。
关键操作是:
- 调用方只依赖抽象类型(
Shape s),不关心具体是谁; - 运行时通过依赖注入、工厂或策略模式注入实际对象;
- 所有分支逻辑(如不同形状的绘制方式)由多态自动分发,不写 if-else。
继承不是敌人,但父类必须足够“瘦”
抽象类不是用来堆业务逻辑的。理想父类只做三件事:定义模板流程、声明抽象方法、提供可选钩子。
比如 ReportGenerator 抽象类中:
-
generate()是 final 模板方法,流程固定; -
fetchData()和export()是 abstract,强制子类实现; -
onSuccess()是 protected 空方法,子类按需重写。
这样新增 CSV 报表,只需写一个 CsvReportGenerator,不碰父类,不改其他子类,也不改调用方代码。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











