里氏替换原则(LSP):子类必须可替换父类
更新时间:2026-08-25。本文从语义与意义两个层面论述里氏替换原则(Liskov Substitution Principle, LSP):它是继承正确性的判据,约束"继承到底是复用还是祸根"。
一、语义:LSP 到底在说什么
LSP 的经典表述(Barbara Liskov, 1987):如果 S 是 T 的子类型,那么所有期望 T 的程序对象都应能毫无察觉地换成 S(If S is a subtype of T, then objects of type T may be replaced with objects of type S without altering any of the desirable properties of the program)。
换成通俗语言:凡是能用基类的地方,用任意子类替换后程序依然正确——客户代码对基类写的契约,子类必须全部兑现。
语义要点:LSP 不是"子类能当父类用"(语法上继承天然如此),而是"子类不能破坏父类定下的契约"。契约由三部分构成:
| 契约项 | 定义 | 子类要求 |
|---|---|---|
| 前置条件 | 调用前必须满足的条件(参数约束) | 只能削弱,不能加强(否则调用方按基类契约传入,子类会拒绝) |
| 后置条件 | 调用后必须成立的条件(返回值、状态) | 只能加强,不能削弱(否则调用方按基类假设消费,会得到错误结果) |
| 不变量 | 对象整个生命周期保持的性质(如余额非负) | 必须全部保持,不得破坏 |
经典反例——正方形不是长方形的特例:Rectangle 设 width=10,height=5 后 area() 应为 50;Square 覆写 setter 强制 w==h,导致 setWidth(10); setHeight(5) 后面积是 100。所有把 Rectangle 当参数的代码换成 Square 后结果都错——这就是 LSP 违反。
二、意义:为什么 LSP 决定继承的存亡
- 多态的前提:OCP 要靠多态实现,而多态的正确性完全建立在 LSP 上——子类不可替换,多态就是"用错误的子类覆盖正确的基类逻辑"。
- 测试的复用:基类的测试套件必须对每个子类可复用(这是 LSP 最实用的检验方法)。违反 LSP,基类测试对子类失效,测试体系崩塌。
- 排障的信任:团队敢不敢在基类里写死逻辑,取决于子类是否守契约。违反 LSP 时,调用方被迫写
if (obj is Square)之类类型判断——类型分支一多,代码退化成过程式。 - 性能视角(本仓库主线):破坏 LSP 的子类通常靠覆写基类方法 + 运行时类型判断(
typeid/instanceof)"兜底",这些判断正是分支预测的噩梦与热点开销来源。
三、违反与守好:对比类图
违反 LSP 的样子——Penguin 继承 Bird 却抛异常:

守住 LSP 的样子——把"会飞"从基类拆出独立接口:

对比分析
| 维度 | 违反 LSP(能力下放基类) | 守住 LSP(接口按能力拆分) |
|---|---|---|
| 契约兑现 | 子类抛异常/改语义,破坏基类契约 | 子类只实现自己承诺的能力 |
| 调用方 | 被迫 instanceof 类型分支兜底 | 只依赖所需的最小接口 |
| 测试 | 基类测试对子类失效 | 每个接口有独立契约与测试 |
| 扩展 | 每加一个"不能飞"的子类就要处理一次 | 加子类选实现接口即可 |
| 演进 | 继承层级越深越脆弱 | 接口组合优于深层继承 |
四、如何判断:实践检查清单
| 症状 | 判断 |
|---|---|
子类方法里 throw / return nullptr / 空实现("不支持此操作") | 已违反,能力不该在基类契约里 |
| 子类覆写后改变参数约束(收更严) | 已违反(加强前置条件) |
| 子类覆写后返回值语义变窄/变弱 | 已违反(削弱后置条件) |
调用方写 if (x instanceof X) 才能干活 | 基类契约被破坏,或抽象层次错误 |
| 基类测试套件对子类全部通过 | ✅ 基本符合 LSP |
边界警示:LSP 与"继承只为复用代码"的冲动直接冲突。能用组合就不要用继承(组合替代继承是常态);非要继承时,先问"子类能否无条件替换基类"。
五、与设计模式的关系
LSP 是模式选型正确性的"守门员":
| 模式 | LSP 的体现 |
|---|---|
| Strategy / State / Template Method | 子类/实现替换上下文后行为一致,多态才有效 |
| Abstract Factory / Factory Method | 工厂产出的产品必须满足产品接口契约 |
| Decorator | 装饰器必须保持被装饰组件的接口语义 |
| Composite | 叶子与容器必须可互换使用 |
| Proxy | 代理必须透明替换真实主题 |
反模式警示:instanceof 分支、空实现、抛异常的子类,几乎都是 LSP 违约信号;此时应重构为"接口按能力拆分",而不是继续加深继承。
一句话总结
里氏替换原则要求子类无条件兑现基类契约——前置条件只能弱化、后置条件只能强化、不变量必须保持;它把"继承"从语法复用升级为语义契约,是 OCP 能成立、多态不出错的根本保障。