OO設計原則 OO設計的LSP里氏替換原則

概要

 Functions that use pointers or references to base classesmust be able to use objects of derived classes without knowing it.

所有引用基類的地方必須能透明地使用其子類的對象。

 

 即:

◇ 所以使用基類代碼的地方,用派生類代碼替換後,能夠正確的執行動作處理。

 

◇ 換句話說,如果派生類替換了基類後,不能夠正確執行動作,那麼他們的繼承關係就應該廢除。

 

 換個說法,只有滿足以下2個條件的OO設計纔可被認爲是滿足了LSP原則:

 

◇ 不應該在代碼中出現if/else之類對子類類型進行判斷的條件。以下代碼就違反了LSP定義。

 

view plaincopy to clipboardprint?

01.if (obj typeof Class1) { 

02.    do something 

03.} else if (obj typeof Class2) { 

04.    do something else 

05.} 

if (obj typeof Class1) {

    do something

} else if (obj typeof Class2) {

    do something else

}

◇ 子類應當可以替換父類並出現在父類能夠出現的任何地方,

 

      或者說如果我們把代碼中使用基類的地方用它的子類所代替,代碼還能正常工作。

 

里氏替換原則LSP是使代碼符合開閉原則的一個重要保證。同時LSP體現了:

 

◇ 類的繼承原則:如果一個繼承類的對象可能會在基類出現的地方出現運行錯誤,

 

       則該子類不應該從該基類繼承,或者說,應該重新設計它們之間的關係。

 

◇ 動作正確性保證:從另一個側面上保證了符合LSP設計原則的類的擴展不會給已有的系統引入新的錯誤。

 

 

 

類的繼承原則:

Robert C. Martin氏在介紹Liskov Substitution Principle (LSP)的原文裏,舉了RectangleSquare的例子。

 

這裏沿用這個例子,但用Java語言對其加以重寫,並忽略了某些細節只列出下面的精要

 

部分來說明裏氏替換原則對類的繼承上的約束。

 

view plaincopy to clipboardprint?

01.//代碼:  

02. 

03.class Rectangle { 

04.    double width; 

05.    doubleheight; 

06.     

07.     

08.    public double getHeight() { 

09.        returnheight; 

10.    } 

11.    public void setHeight(doubleheight) { 

12.        this.height = height; 

13.    } 

14.    public double getWidth() { 

15.        returnwidth; 

16.    } 

17.    public void setWidth(doublewidth) { 

18.        this.width = width; 

19.    }   

20.} 

21. 

22.class Square extends Rectangle { 

23.    public void setHeight(doubleheight) { 

24.        super.setHeight(height); 

25.        super.setWidth(height); 

26.    } 

27.     

28.    public void setWidth(doublewidth) { 

29.        super.setHeight(width); 

30.        super.setWidth(width); 

31.    } 

32.} 

//代碼:

 

class Rectangle {

    double width;

    doubleheight;

   

   

    public double getHeight() {

        returnheight;

    }

    public void setHeight(doubleheight) {

        this.height = height;

    }

    public double getWidth() {

        returnwidth;

    }

    public void setWidth(doublewidth) {

        this.width = width;

    } 

}

 

class Square extends Rectangle {

    public void setHeight(doubleheight) {

        super.setHeight(height);

        super.setWidth(height);

    }

   

    public void setWidth(doublewidth) {

        super.setHeight(width);

        super.setWidth(width);

    }

}

這裏Rectangle是基類,SquareRectangle繼承。

這種繼承關係有什麼問題嗎?

 

假如已有的系統中存在以下既有的業務邏輯代碼:

 

view plaincopy to clipboardprint?

01.void g(Rectangle r) { 

02.    r.setWidth(5); 

03.    r.setHeight(4); 

04.    if (r.getWidth() * r.getHeight() != 20) { 

05.        throw new RuntimeException(); 

06.    } 

07.} 

void g(Rectangle r) {

    r.setWidth(5);

    r.setHeight(4);

    if (r.getWidth() * r.getHeight() != 20) {

        throw new RuntimeException();

    }

}

則對應於擴展類Square,在調用既有業務邏輯時:

 

view plaincopy to clipboardprint?

01.Rectangle square = newSquare(); 

02.g(square); 

        Rectangle square = newSquare();

        g(square);

時會拋出一個RuntimeException異常。這顯然違反了LSP原則。

 

 

 

 

動作正確性保證:

因爲LSP對子類的約束,所以爲已存在的類做擴展構造一個新的子類時,

 

根據LSP的定義,不會給已有的系統引入新的錯誤。

 

Design by Contract

 

根據Bertrand Meyer氏提出的Design by ContractDBC:基於合同的設計)概念的描述,

 

對於類的一個方法,都有一個前提條件以及一個後續條件,前提條件說明方法接受什麼樣的參數數據等,

 

只有前提條件得到滿足時,這個方法才能被調用;同時後續條件用來說明這個方法完成時的狀態,

 

如果一個方法的執行會導致這個方法的後續條件不成立,那麼這個方法也不應該正常返回。

 

現在把前提條件以及後續條件應用到繼承子類中,子類方法應該滿足:

 

1)前提條件不強於基類.

 

2)後續條件不弱於基類.

 

換句話說,通過基類的接口調用一個對象時,用戶只知道基類前提條件以及後續條件。

 

因此繼承類不得要求用戶提供比基類方法要求的更強的前提條件,

 

亦即,繼承類方法必須接受任何基類方法能接受的任何條件(參數)。

 

同樣,繼承類必須順從基類的所有後續條件,亦即,

 

繼承類方法的行爲和輸出不得違反由基類建立起來的任何約束,不能讓用戶對繼承類方法的輸出感到困惑。

 

這樣,我們就有了基於合同的LSP,基於合同的LSPLSP的一種強化。

 

在很多情況下,在設計初期我們類之間的關係不是很明確,

 

LSP則給了我們一個判斷和設計類之間關係的基準:需不需要繼承,以及怎樣設計繼承關係。

 

總結

 

LSP: 子類必須能夠替換基類。

 

 

Subtypes must besubstitutable  for their base types.

 

 1.      LSP關注的是怎樣良好的使用繼承.

 

 2.      必須要清楚是使用一個Method還是要擴展它,但是絕對不是改變它。

 

 3.      LSP清晰的指出,OODIS-A關係是就行爲方式而言,

 

          行爲方式是可以進行合理假設的,是客戶程序所依賴的。

 

 4.      LSP讓我們得出一個重要的結論:一個模型如果孤立的看,並不具有真正意義的有效性。

 

          模型的有效性只能通過它的客戶程序來表現。必須根據設計的使用者做出的合理假設來審視它。

 

           而假設是難以預測的,直到設計臭味出現的時候才處理它們。

 

 5.      對於LSP的違反也潛在的違反了OCP

 

 

 

發佈了26 篇原創文章 · 獲贊 55 · 訪問量 2萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章