《設計模式》——里氏替換原則
1、什麼是里氏替換原則
Liskov於1987年提出了一個關於繼承的原則“Inheritance should ensure that any property proved about supertype objects also holds for subtype objects.”——“繼承必須確保父類所擁有的性質在子類中仍然成立。”也就是說,當一個子類的例項應該能夠替換任何其父類的例項時,它們之間才具有is-A關係。該原則稱為Liskov Substitution Principle——里氏替換原則。
2、程式碼示例
正方形不是長方形:
在我們的認知範圍內,長方形的長不等於寬,正方形是長等於寬的長方形,正方形是一種特殊的長方形。但在實際編碼過程中遇到的情況是怎麼樣的呢,通過程式碼分析:
package cn.com.design.model.test.liskvorole.negative;
/**
* 專案名稱:OpenCloseRole
* 類 名 稱:Test
* 類 描 述:TODO
* 建立時間:2021/1/18 下午8:32
* 創 建 人:wteng
*/
/**
* 在我們的認知範圍內,一致認為正方形就是一個特殊的長方形
* 我們也知道,兩個類只要有"is a"關係,兩個類就可以發生繼承關係。
* 里氏替換原則:任何使用父類的地方,都能被透明的替換成子類。替換成子類後,程式行為不會發生問題。
* 在里氏替換原則的指導方針下,可得出:僅僅依據兩個類之間有沒有"is a"的關係,來判斷兩個類能不能發生繼承關係,是不夠的,
*
* 以下是反例,正方形不能替換長方形,替換後測試失敗
*/
class Rect {
private int width;
private int height;
public int getWidth() {
return width;
}
public void setWidth(int width) {
this.width = width;
}
public int getHeight() {
return height;
}
public void setHeight(int height) {
this .height = height;
}
public int area() {
return getHeight()*getWidth();
}
}
class Squaer extends Rect{
private int sideLength;
@Override
public void setWidth(int width) {
this.sideLength = width;
}
@Override
public void setHeight(int height) {
this.sideLength = height;
}
@Override
public int getWidth() {
return sideLength;
}
@Override
public int getHeight() {
return sideLength;
}
}
class Foo {
public void testArea(Rect rect) {
rect.setWidth(10);
rect.setHeight(15);
if ((rect.area() == 150)) {
System.out.println("測試通過");
} else {
System.out.println("測試失敗");
}
}
}
public class Test {
public static void main(String[] args) {
Foo foo = new Foo();
// Rect rect = new Rect();
Rect squer = new Squaer();
foo.testArea(squer);
}
}
但是執行結果為測試失敗,說明正方形是特殊的長方形違背了里氏替換原則,為了解決上邊問題,僅僅斷絕square和Rect的繼承關係即可
除此之外,類似經典的案例還有鴕鳥飛鳥。
3.總結
所謂物件是一組狀態和一系列行為的組合。狀態是物件的內在特性,行為是物件的外在特性。LSP所表述的就是在同一個繼承體系中的物件應該有共同的行為特徵。我們在設計物件時是按照行為進行分類的,只有行為一致的物件才能抽象出一個類來。設定長方形的長度的時候,它的寬度保持不變,設定寬度的時候,長度保持不變。正方形的行為:設定正方形的長度的時候,寬度隨之改變;設定寬度的時候,長度隨之改變。所以,如果我們把這種行為加到基類長方形的時候,就導致了正方形無法繼承這種行為。我們“強行”把正方形從長方形繼承過來,就造成無法達到預期的結果。鴕鳥非鳥,能飛是鳥的特性,但鴕鳥是不能飛的,我們強行將其歸為鳥類,最終導致程式碼出錯。
所有子類的行為功能必須和其父類持一致,如果子類達不到這一點,那麼必然違反里氏替換原則。在實際的開發過程中,不正確的派生關係是非常有害的。伴隨著軟體開發規模的擴大,參與的開發人員也越來越多,每個人都在使用別人提供的元件,也會為別人提供元件。最終,所有人的開發的元件經過層層包裝和不斷組合,被整合為一個完整的系統。每個開發人員在使用別人的元件時,只需知道元件的對外裸露的介面,那就是它全部行為的集合,至於內部到底是怎麼實現的,無法知道,也無須知道。所以,對於使用者而言,它只能通過介面實現自己的預期,如果元件介面提供的行為與使用者的預期不符,錯誤便產生了。里氏替換原則就是在設計時避免出現派生類與基類不一致的行為。