太好了,現在我們來講解 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 嗎?實戰會更直觀哦。你想繼續擴展哪部分?🔍