里氏替换原则的核心是“能安全替换”:所有使用父类类型的地方,换成任意子类对象,程序行为必须完全一致,不报错、不崩溃、不改变逻辑,本质是行为契约的严格守约。

里氏替换原则的核心不是“能继承”,而是“能安全替换”
里氏替换原则(LSP)不是教你怎么写子类,而是划一条底线:只要代码里用的是父类类型,换成任意一个子类对象,程序运行结果必须一模一样——不报错、不崩溃、不返回错误值、不改变业务逻辑。这背后不是语法兼容,而是行为契约的严格守约。比如一个calculateDiscount()方法承诺“对VIP用户打九折”,子类重写后若对某类VIP突然变成八五折,哪怕语法全对,也已违反LSP。
违反LSP的典型信号,往往藏在运行时而非编译期
Python这类动态语言不会在定义时拦住你,但以下现象是危险信号:
- 调用方传入子类对象后,原本稳定的if isinstance(obj, Parent)分支被绕过,或触发未预期的AttributeError
- 父类方法文档说“支持所有正整数输入”,子类实现却在输入为100时抛出ValueError
- 父类save()方法保证事务原子性,子类重写后拆成两步操作,中间可能被中断
- 测试用例只覆盖父类逻辑,一接入子类就出现面积算错、状态机跳转异常等“逻辑漂移”
真正落地LSP,关键在契约设计而非继承语法
不要从“这两个类长得像”出发决定继承,而要问三个问题:
- 这个父类是否定义了明确、稳定、可验证的行为契约?(例如:set_width()之后get_area()必须立即反映新宽度)
- 所有子类能否无条件满足该契约的所有前置条件与后置条件?(如:父类允许width=0,子类就不能强制要求width>0)
- 有没有更轻量的替代方案?比如用组合+接口(Shape接口下Rectangle和Square各自独立实现),比强行让Square继承Rectangle更可靠
重构违反LSP的设计,比修复bug更值得投入时间
当发现子类不得不在重写方法中加if type(self) is Square:这类判断,或频繁抛出NotImplementedError来规避父类逻辑时,说明继承关系已失真。此时应:
- 将原父类抽象为接口或抽象基类,只声明契约,不提供具体实现
- 把共用逻辑抽到工具函数或独立服务中,由各类按需调用
- 用委派(delegation)代替继承:让Square持有一个Rectangle实例,仅复用其计算能力,不承担其行为义务

















