太好了,现在我们来讲解 SOLID 中非常核心的 LSP:里氏替换原则(Liskov Substitution Principle)。
我会一步步讲清楚:
- 什么是 LSP?
- 为什么重要?
- 优劣分析
- Python 正反例子
- 清晰的结构图(Mermaid)
🧠 一句话定义(LSP)
任何父类出现的地方,都应该可以用它的子类替代,并且不会导致程序逻辑出错。
简化记忆:
子类能替换父类,并保持行为正确。
🎯 为什么需要?
当你用多态写程序(比如用父类来调用子类对象)时:
如果子类违背了父类的行为约定,会导致系统运行时出现不符合预期的错误,就违反了 LSP。
✅ 优点 vs ❌ 缺点
优点 | 缺点 |
---|---|
子类更符合父类语义 | 设计成本提升 |
多态行为更安全 | 有时限制了子类个性 |
程序行为更稳定 | 实现复杂逻辑更麻烦 |
🔥 常见违反 LSP 的坑
子类复写方法后,行为和父类完全不同、甚至反逻辑。
❌ 违反 LSP 的反面例子
class Bird:def fly(self):print("I can fly")class Ostrich(Bird):def fly(self):raise Exception("I can't fly") # ❌ 鸵鸟不能飞def let_it_fly(bird: Bird):bird.fly()let_it_fly(Ostrich()) # ❌ 虽然语法对,但运行崩了
问题:
Ostrich
是Bird
,但替换后程序出错 → 违反 LSP
✅ 遵守 LSP 的正确做法(更合理抽象)
from abc import ABC, abstractmethod# 抽象出“会飞的鸟”和“不飞的鸟”
class Bird(ABC):@abstractmethoddef eat(self):passclass Flyable(ABC):@abstractmethoddef fly(self):passclass Sparrow(Bird, Flyable):def eat(self): print("Sparrow eats")def fly(self): print("Sparrow flies")class Ostrich(Bird):def eat(self): print("Ostrich eats")# 没有 fly 方法# ✅ 函数只接受会飞的鸟
def let_it_fly(bird: Flyable):bird.fly()let_it_fly(Sparrow()) # ✅ OK
# let_it_fly(Ostrich()) # ❌ 语法报错,避免运行期出错
通过接口分离 + 更精确抽象,让程序在编译期就避免 LSP 问题。
🧭 结构图(Mermaid)
🏁 总结口诀
原则 | 理解方式 | 记忆口诀 |
---|---|---|
✅ 里氏替换原则 | 子类能替换父类,行为不崩溃 | “不是你的儿子,不要继承你” |
🚨 如何避免 | 抽象设计精准、使用接口拆分 | “不要滥用继承,改用组合或接口” |
📌 实际应用场景
- 游戏角色:近战 vs 远程,应分成独立能力而不是强行继承
- 网络传输协议:TCP vs UDP,公共方法和行为应明确分离
- 交通工具:汽车 vs 船,不要硬继承“能跑的交通工具”
需要我帮你写一个 支付系统 或 用户系统中角色模型 来体现 LSP 吗?实战会更直观哦。你想继续扩展哪部分?🔍