首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

里氏替換原則(LSP)在 Java 設計模式中的意義與實例解析

     編輯:Java 相關知識

定義

若對每一個類型為 T1 的物件 o1,都有類型為 T2 的物件 o2,使得以 T1 定義的所有程式 P 在所有 o1 替換為 o2 時,程式 P 的行為未發生變化,則 T2 是 T1 的子類型。
—— Barbara Liskov, 1988

通俗說:子類別必須能替換其父類別,且不影響程式正確性。

四層含義

  1. 子類可實現父類抽象方法,但不能覆寫父類非抽象方法。
  2. 子類可新增獨有方法。
  3. 子類覆寫/實現方法時,前置條件(參數)不得強於父類(較寬鬆)。
  4. 子類實現方法時,後置條件(返回值)不得弱於父類(較嚴格)。

違反 LSP 的經典反例:正方形繼承長方形

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 與設計模式的關係

  • 策略模式:策略介面的各實現類必須符合 LSP,客戶端才能安心切換策略。
  • 模板方法:父類定義骨架,子類實現細節步驟,必須遵守父類協定。
  • 工廠方法:工廠返回的產品子類必須可替換抽象產品。

實踐檢查清單

  1. 是否覆寫了父類非抽象方法?若是,評估是否破壞契約。
  2. 子類方法參數是否比父類更寬鬆?返回值是否更嚴格?
  3. 是否引入父類未宣稱的新異常或副作用?
  4. 能否用「組合/介面」替代「繼承」來建模「is-a」關係?

總結

里氏替換原則是繼承體系健全性的基石。違反 LSP 的代碼在多態調用時會產生難以預測的 Bug,修復成本極高。設計時應嚴守契約,寧可用組合、介面隔離,也不要為了復用而濫用繼承。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved