若對每一個類型為 T1 的物件 o1,都有類型為 T2 的物件 o2,使得以 T1 定義的所有程式 P 在所有 o1 替換為 o2 時,程式 P 的行為未發生變化,則 T2 是 T1 的子類型。
—— Barbara Liskov, 1988
通俗說:子類別必須能替換其父類別,且不影響程式正確性。
class Rectangle {
protected int width, height;
public void setWidth(int w) { width = w; }
public void setHeight(int h) { height = h; }
public int area() { return width * height; }
}
class Square extends Rectangle {
public void setWidth(int w) { width = height = w; }
public void setHeight(int h) { width = height = h; }
}
// 客戶端代碼
void resize(Rectangle r) {
r.setWidth(10);
r.setHeight(5);
assert r.area() == 50; // 傳入 Square 時斷言失敗!
}
問題:Square 覆寫了 setter,破壞了 Rectangle「寬高可獨立變化」的契約,導致基於 Rectangle 協定的客戶端代碼失效。
interface Shape { int area(); }
class Rectangle implements Shape { /* ... */ }
class Square implements Shape { /* 內部封裝邊長,不繼承 Rectangle */ }
class Bird { void fly() { /* 飛行 */ } }
class Penguin extends Bird {
void fly() { throw new UnsupportedOperationException("企鵝不會飛"); } // 違反 LSP
}
客戶端呼叫 bird.fly() 預期正常完成,傳入 Penguin 卻拋出異常,破壞行為一致性。
里氏替換原則是繼承體系健全性的基石。違反 LSP 的代碼在多態調用時會產生難以預測的 Bug,修復成本極高。設計時應嚴守契約,寧可用組合、介面隔離,也不要為了復用而濫用繼承。