里氏替换原则要求子类可替换父类且行为不变;违反时应重构为组合+接口,如bird与ostrich改用flyable接口或策略模式,确保所有子类能安全传入父类型参数而无需类型判断。

里氏替换原则(Liskov Substitution Principle,LSP)要求子类对象能够替换父类对象,且程序行为不变。当继承关系违反这一原则时,比如子类重写了父类方法却改变了语义、抛出更宽泛异常、缩小前置条件或扩大后置条件,就说明继承树设计不合理——此时不能靠“硬撑”继承,而应重构。
识别违反LSP的典型信号
以下情况往往意味着继承关系失当:
- 子类中大量覆写父类方法,并在方法里直接抛出 UnsupportedOperationException 或返回 null
- 父类定义了“通用行为”,但某些子类根本无法实现(如 Rectangle 继承 Square 后,setHeight() 和 setWidth() 无法独立调用)
- 客户端必须用 instanceof 或强制类型转换来区分子类并特殊处理
- 测试父类逻辑时,某个子类反复失败,而失败原因不是bug,而是语义冲突
用组合+接口替代“伪继承”
当子类只是“拥有”父类部分能力,而非真正意义上的“是一种”,优先考虑拆解职责:
- 把原父类中可复用的行为提取为接口(如 Movable、Drawable),让各类实现所需接口
- 用组合代替继承:例如原本 Bird extends Animal,但 Ostrich 不能飞,就不要让 Ostrich 继承含 fly() 的 Animal;改为 Bird 持有 FlightBehavior 实例,Ostrich 则持有 NullFlightBehavior
- 保留继承仅用于真正满足“is-a”且行为可替换的场景(如 ArrayList 和 LinkedList 都是 List 的合理实现)
重构示例:从错误继承到合理分层
假设原有代码:
class Bird { void fly() { /* 默认飞行 */ } }
class Ostrich extends Bird {
@Override void fly() { throw new UnsupportedOperationException(); }
}
问题:调用 bird.fly() 时,传入 Ostrich 就崩溃,违反LSP。
重构后:
- 定义接口 interface Flyable { void fly(); }
- class Sparrow implements Flyable,class Ostrich 不实现 Flyable
- 若需统一管理,用容器区分:List
flyers = ... ,ListallBirds = ... - 必要时引入策略模式:让 Bird 持有 FlightStrategy,不同鸟设置不同策略
验证重构是否符合LSP
重构后,检查以下几点是否成立:
- 所有子类对象能安全传入期望父类/接口类型参数的方法中,无需额外判断
- 子类方法的参数类型不比父类更严格(协变参数不允许,但Java 8+ 允许协变返回类型)
- 子类方法不抛出父类方法未声明的受检异常
- 子类方法的后置条件(如返回值范围、状态变更)不弱于父类(例如父类保证返回非null,子类也必须)
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











