LSP 由 Barbara Liskov 於 1988 年提出,是定義「子型別(Subtype)」最嚴謹的規則。 在架構層次上,它不僅關乎繼承,更關乎介面與實作的契約遵守。
LSP 定義:
「若 S 是 T 的子型別,則 T 的物件可以被 S 的物件替換,而不會改變程式原本應有的正確行為。」 (Subtypes must be substitutable for their base types.)
簡單來說:「如果它看來像鴨子,叫來像鴨子,但卻需要電池才能運作,那它就違反了 LSP。」 因為使用者預期的是一隻真鴨子,錯用結果會導致系統崩潰。

Figure 9.1: License and its derivatives conform to LSP
一、經典悖論:正方形是長方形嗎?#
在數學幾何上,正方形(Square)是種長方形(Rectangle)。
但在程式設計的行為上,它不是。
- 長方形的契約: 設定
width和height是獨立的。修改寬度不應影響高度 - 正方形的行為: 為了維持正方形特性,修改
width必須同時改變height
違反 LSP 的後果:
如果呼叫端(User)持有的是 Rectangle 參考,卻被傳入一個 Square 實例。
當 User 設定寬度時,會驚訝地發現高度也變了,導致計算面積錯誤。
為修復這 Bug,User 須在程式碼加入 if (type == Square) 的檢查——這就是架構被污染的開始。

Figure 9.2: The infamous square/rectangle problem
二、架構層次的 LSP:計程車調度案例#
LSP 不僅適用於物件導向的繼承,也適用於系統架構間的介面互動。
Uncle Bob 舉了一個「計程車調度服務」例子:
假設我們正開發一個透過 REST API 呼叫不同計程車行的系統。
- 約定 URI:
purplecab.com/driver/Bob/pickup/destination/123 - Acme 計程車行的違規: 他們的工程師決定不遵守約定,把
destination縮寫成dest
架構的污染#
為了支援這家違規的 Acme 公司,我們的系統核心邏輯被迫加入特殊判斷:
if (driver.getCompany().equals("Acme")) {
dispatchUrl = ".../dest/..."; // 特例處理
} else {
dispatchUrl = ".../destination/..."; // 標準處理
}