1 | # 里氏替换原则(Liskov Substitution Principle, LSP)详解 |
问题:Penguin 是 Bird 的子类,但它不能替代 Bird 被正常使用。
这就违反了里氏替换原则。
三、访问修饰符的例子
子类重写父类方法时,访问修饰符可以更宽松,但不能更严格。
正确示例(推荐):
1 | class Animal { |
1 | void feedAnimal(Animal animal) { |
注意:Java 中 protected 方法在不同包的子类中不可直接通过父类引用访问。
但更关键的是 行为一致性,而不仅是修饰符。
四、LSP 的核心:行为契约(Contract)
LSP 不仅仅是语法兼容,更是 行为契约 的延续。
| 父类承诺 | 子类必须遵守 |
|---|---|
| 前置条件 | 不应增强(不能要求更多) |
| 后置条件 | 不应削弱(必须保证更多或相等) |
| 不变式 | 必须保持 |
五、经典反例:正方形 vs 矩形
1 | class Rectangle { |
结论:Square 不是 Rectangle 的子类型(在行为上)!
六、如何遵守 LSP?—— 设计建议
1. 避免子类破坏父类行为
1 | // 错误:子类抛出父类未声明的异常 |
2. 子类不应增强前置条件
1 | class FileReader { |
3. 使用接口分离 + 依赖倒置
1 | interface Flyable { |
七、LSP 与访问修饰符的关系(回到你的图片)
| 父类修饰符 | 子类允许 | 不允许 | 原因 |
|---|---|---|---|
public |
public |
protected, private |
不能降低可见性 |
protected |
protected, public |
private |
可以放宽,不能收紧 |
private |
—— | —— | 不可重写 |
正确示例(Java):
1 | class Parent { |
八、总结:LSP Checklist
| 检查项 | 是否合规 |
|---|---|
| 子类是否能完全替代父类? | 是 |
| 子类是否增强前置条件? | 否 |
| 子类是否削弱后置条件? | 否 |
| 子类是否收紧访问权限? | 否 |
| 子类是否抛出未声明异常? | 否 |
九、参考资料
- 《Clean Architecture》 - Robert C. Martin
- Liskov Substitution Principle - Wikipedia
- SOLID Principles
记住:
“继承不是为了复用代码,而是为了可替换性。”
—— LSP 告诉我们:设计时先问“能不能替换”,再问“能不能复用”。
本文适用于 Java、TypeScript、C# 等支持面向对象的语言。
---