里氏替换原则是什么
里氏替换原则(LSP)要求子类型能替换基类型而不破坏程序正确性,这是 Liskov 与 Wing 给出的行为子类型表述:子类不得加强前置条件、不得削弱后置条件,也不得破坏基类的不变量。它约束的是行为契约,不是语法上的继承关系——能编译通过不代表能安全替换。
基本信息
| 提出 | Barbara Liskov(1987 年主题演讲,1994 年与 Wing 形式化) |
|---|---|
| 契约规则 | 前置条件不加强、后置条件不削弱、不变量保持 |
| 经典反例 | 正方形继承矩形:改宽会顺带改高 |
| 检查方式 | 调用方对基类成立的推理,在子类上是否仍然成立 |
典型使用场景
- 子类为父类方法抛出不支持异常:契约被截断
- 子类收紧了参数校验:替换后在边缘输入上崩溃
- 子类改变返回值语义:调用方的断言随之失效
- 为了复用实现而继承:本该是组合的位置用了继承
- 第三方类型无法修改:改用适配器或组合对象暴露接口
契约违背示例
契约违背示例
// 矩形:改宽不影响高,调用方据此推理
Rect r = new Square();
r.setWidth(5);
r.setHeight(4);
assert r.area() == 20; // 在 Square 下不成立,契约被破坏
// 替代方案:让两者平级,各自实现接口
interface Shape { int area(); }常见违背形式
| 违背形式 | 表现 | 处理方向 |
|---|---|---|
| 抛出不支持异常 | 子类只实现了部分接口 | 拆细接口,或改用组合 |
| 加强前置条件 | 子类额外要求参数非空 | 把校验上移到基类契约中说明 |
| 削弱后置条件 | 返回值范围比基类更大 | 保持或收窄返回语义 |
| 破坏不变量 | 子类改变了状态之间的关系 | 重新审视继承关系是否成立 |
LSP 是行为子类型而非语法子类型:编译通过只说明类型兼容,并不代表替换之后语义安全。
协变返回与逆变参数这类语言层规则只是必要条件,真正的判据仍是调用方能否保持原有的推理。