里氏替换原则要求子类对象能透明替换父类对象且不改变程序正确性,核心是守恒行为契约(输入输出、前置/后置条件、不变量),允许功能增强但禁止削弱语义或破坏调用方预期。

里氏替换原则(LSP)的核心不是“子类不能破坏父类逻辑”,而是子类对象必须能透明地替换父类对象,且不改变程序的正确性。关键在于行为契约的守恒——父类定义了什么行为,子类在继承后仍要满足同样的输入输出关系、前置条件、后置条件和不变量。
行为契约比代码逻辑更重要
父类方法的文档注释、参数约束、返回值语义、异常类型、线程安全性等,共同构成隐式或显式的行为契约。子类重写时若放宽前置条件(如允许 null 输入)、加强后置条件(如额外抛出异常)、改变可变状态的可见性,就可能违反 LSP。
- 例如:父类
withdraw(double amount)规定“金额必须大于 0”,子类却允许 0 或负数并静默忽略——调用方依赖非零校验的逻辑就会出错。 - 又如:父类方法保证线程安全,子类去掉同步机制,外部并发调用结果不可预测。
子类可以扩展,但不能削弱
LSP 允许子类增强功能(如缓存、日志、更精确的异常),但不能缩小能力范围或改变原有语义。
- 父类返回
List<String>,子类返回ArrayList<String>是安全的;但若子类返回空列表或抛出UnsupportedOperationException就违规。 - 父类方法承诺“不修改入参对象”,子类却调用了
input.clear(),破坏了调用方对参数状态的预期。
测试是验证 LSP 的有效手段
为父类编写单元测试(尤其是边界和异常场景),然后用相同测试套件运行子类实例。若子类测试失败,大概率违反 LSP。
- 测试应覆盖:正常流程、非法输入、空值、并发访问、状态变更前后的一致性。
- 避免只测“能跑通”,而要测“行为是否等价”——比如父类抛
IllegalArgumentException,子类改抛RuntimeException就属于契约破坏。
真正遵守 LSP,靠的不是限制子类做什么,而是让设计者始终以“使用者视角”审视继承关系:当把子类传给只认识父类的代码时,它会不会悄悄做些父类从不做的事,或者拒绝做父类承诺过的事?













