首页/开发/系统分析与架构设计速查/里氏替换原则(LSP)
架构风格与模式58 条

里氏替换原则(LSP)

子类型必须能替换父类型而不破坏调用方依赖的行为契约:前置条件不加强、后置条件不减弱、不变量保持;典型违反是正方形继承矩形。

里氏替换原则是什么

里氏替换原则(LSP)要求子类型能够替换父类型而不破坏调用方所依赖的行为契约:子类可以放宽前置条件、可以加强后置保证,但不能反过来——不能对入参提出更严格的要求,也不能削弱对结果的承诺,更不能不保持父类的不变量。判断是否违反有个实用方法:看调用方是否需要做类型判断或写特殊分支才能正确工作。典型反例是让正方形继承矩形,看起来符合直觉,实际破坏了调用方对宽高独立变化的假设。

基本信息

契约规则子类可放宽前置条件、可加强后置保证,不可反向
不变量父类承诺始终成立的约束,子类必须保持
异常约定子类不应抛出父类未声明的异常
识别方法调用方是否需要 instanceof 判断或特殊分支才能正确工作
修复方向改为组合、不可变值对象或重新设计继承层级

典型使用场景

  • 继承层级评审:检查子类是否改变了父类的语义而不是只扩展行为
  • 接口实现约定:实现类不能悄悄收窄参数范围或加强校验
  • 多态调用链路:替换实现后调用方逻辑是否需要特判
  • 测试替身设计:桩对象的行为要符合真实实现的契约,否则测试失真
  • 框架扩展点:插件实现同一接口时,行为契约必须写在文档里
  • 重构决策:判断某处继承关系是否应改为组合

契约与违反表现

契约项含义违反时的表现
前置条件调用方必须满足的输入约束子类要求更多输入,旧调用方报错
后置条件方法返回后必须成立的保证子类返回弱化结果或部分完成
不变量对象生命周期内恒成立的约束子类破坏字段间关系,状态不一致
异常约定允许抛出的异常集合子类抛出新异常,调用方处理逻辑失效
副作用约定对外部状态与调用顺序的影响子类悄悄改变了状态变更顺序

违反与修复示例

违反与修复示例
# 违反:子类改变父类语义
class 矩形 { setW(w); setH(h) }        # 宽高可独立变化
class 正方形 extends 矩形 { setW 同时改高 }  # 调用方假设被破坏
# 现象:调用方连续设置宽高后,读到的尺寸与预期不符

# 修复方向一:取消继承,改为不可变值对象与工厂方法
# 修复方向二:抽出更小的接口(如面积计算),分别实现,不做父子关系
# 检验:调用方不再需要 instanceof 或特殊分支即可正确工作
行为契约比方法签名更重要:签名兼容但语义不同(例如子类把空值处理改为抛异常)同样违反 LSP,这类问题往往在替换实现后才暴露。
继承表达的是「是一种、且行为可替换」;如果只是复用代码,组合通常更安全,也更不容易破坏调用方的既有假设。

易错点

  • 用类型判断分支处理不同子类:说明继承层级设计有问题,多态没有真正生效
  • 子类重写方法抛出新异常:调用方处理逻辑失效,错误向上冒出
  • 子类加强入参校验:父类型入参在子类被拒绝,替换后调用失败
  • 为了复用代码而非语义关系使用继承:契约被静默改变,问题在替换时集中暴露
  • 接口文档不写行为约定:实现者只按签名实现,语义差异无人发现

数据来源与参考

  • Barbara Liskov & Jeannette Wing《A Behavioral Notion of Subtyping》(1994)
  • Robert C. Martin《Clean Architecture》
​‌‌​​‌​​​‌‌​‌‌‌‌​‌‌​​​‌‌​‌‌‌​​‌‌​​‌​‌‌‌​​‌‌‌​​​‌​‌‌​‌​​‌​‌‌‌​​​‌​‌‌​‌​​‌​‌‌​​​‌‌​‌‌‌​‌​​​‌‌​‌‌‌‌​​‌​‌‌‌​​‌‌​​​‌‌​‌‌​‌‌‌‌​‌‌​‌‌​‌