里氏替换原则(LSP)是约束继承合理性的设计准则,而非利用继承的工具;应仅在符合LSP时使用继承,否则优先采用组合、接口或抽象类,确保子类不破坏父类契约、支持运行时多态且避免伪继承。

里氏替换原则(LSP)不是用来“利用继承”的工具,而是用来约束继承是否合理的设计准则。真正提升系统扩展性的关键,是把继承用对——即只在符合 LSP 的前提下使用继承,否则就该用组合、接口或抽象类替代。
用抽象类或接口定义契约,而非具体实现
父类不该是普通类,而应是抽象类或接口,只声明行为,不固化逻辑。这样子类才能自由实现,又不破坏调用方预期。
- 比如定义 PaymentStrategy 接口,含
process()方法;子类WechatPay、Alipay、CreditCardPay各自实现,互不影响 - 若父类是具体类
BasePayment并已实现process(),子类重写就容易违背 LSP(比如改了异常类型、缩小了输入范围、改变了后置条件) - 调用方只依赖
PaymentStrategy,新增支付方式只需加新类,无需改原有代码
子类必须完整实现父类契约,不能“删功能”或“改语义”
父类方法的前置条件(输入范围)、后置条件(返回值、异常、副作用)是契约。子类可以放宽输入、缩小输出,但不能反过来。
- 父类方法声明
Map<String, Object> getData(),子类可返回HashMap(满足),但不能返回LinkedHashMap并额外保证插入顺序(除非契约明确允许) - 父类方法接受
List<User>,子类方法参数改为ArrayList<User>就违规——调用方传LinkedList会编译失败或运行报错 - 若某子类无法实现某个父类方法(如
Gun.shoot()对ToyGun不适用),说明继承关系错误,应拆出Playable和Fireable两个接口
运行时多态代替硬编码类型判断
所有使用父类型的地方,都应能直接传入任意子类实例,且不加 instanceof 或类型强转。
立即学习“Java免费学习笔记(深入)”;
- 错误写法:
if (obj instanceof Manager) { ... } else if (obj instanceof Staff) { ... }—— 违反开闭原则,也隐含 LSP 失效(否则何必区分?) - 正确写法:定义
Employee抽象类,含抽象方法calculateSalary();各子类自行实现;业务层统一调用emp.calculateSalary() - 这样加新员工类型(如
Contractor)只需新增子类,不碰老逻辑
拒绝“伪继承”:当子类只是部分相似,就别硬拉父子关系
继承表达的是“is-a”,不是“has-a”或“uses-a”。强行继承会制造脆弱的耦合。
- 例子:
Ostrich(鸵鸟)继承Bird(鸟类),但不会飞——若Bird.fly()是非抽象方法,Ostrich要么抛异常、要么空实现,调用方一旦依赖fly()就崩溃 - 解法:把
fly()提到Flyable接口中,Sparrow实现它,Ostrich不实现;两者共同继承更上层的Animal - 这种分层+组合的设计,让扩展更自然:要支持滑翔,加
Glideable;要支持潜水,加Diveable


















