在Java中,支持抽象和多態(tài)的關(guān)鍵機制之一是繼承。正是使用了繼承,我們才可以創(chuàng)建實現(xiàn)父類中抽象方法的子類。那么,是什么規(guī)則在支配著這種特殊的繼承用法呢?最佳的繼承層次的特征又是什么呢?在什么情況下會使我們創(chuàng)建的類層次結(jié)構(gòu)掉進不符合開閉原則的陷阱中呢?這就是里氏替換原則要解決的問題。
概念
里氏代換原則中說,任何基類可以出現(xiàn)的地方,子類一定可以出現(xiàn)。 LSP是繼承復用的基石,只有當衍生類可以替換掉基類,軟件單位的功能不受到影響時,基類才能真正被復用,而衍生類也能夠在基類的基礎(chǔ)上增加新的行為。
里氏代換原則是對“開-閉”原則的補充。實現(xiàn)“開-閉”原則的關(guān)鍵步驟就是抽象化。而基類與子類的繼承關(guān)系就是抽象化的具體實現(xiàn),所以里氏代換原則是對實現(xiàn)抽象化的具體步驟的規(guī)范。
簡單的理解為一個軟件實體如果使用的是一個父類,那么一定適用于其子類,而且它察覺不出父類對象和子類對象的區(qū)別。也就是說,軟件里面,把父類都替換成它的子類,程序的行為沒有變化。
子類型必須能夠替換掉它們的父類型。
- 類的繼承原則:里氏替換原則常用來檢查兩個類是否為繼承關(guān)系。在符合里氏替換原則的繼承關(guān)系中,使用父類代碼的地方,用子類代碼替換后,能夠正確的執(zhí)行動作處理。換句話說,如果子類替換了父類后,不能夠正確執(zhí)行動作,那么他們的繼承關(guān)系就是不正確的,應(yīng)該重新設(shè)計它們之間的關(guān)系。
- 動作正確性保證:里氏替換原則對子類進行了約束,所以在為已存在的類進行擴展,來創(chuàng)建一個新的子類時,符合里氏替換原則的擴展不會給已有的系統(tǒng)引入新的錯誤。
繼承的優(yōu)缺點
優(yōu)點:
- 代碼共享,減少創(chuàng)建類的工作量,每個子類都擁有父類的方法和屬性
- 提高代碼的重用性
- 子類可以形似父類,但是又異于父類。
- 提高代碼的可擴展性,實現(xiàn)父類的方法就可以了。許多開源框架的擴展接口都是通過繼承父類來完成。
- 提高產(chǎn)品或項目的開放性
缺點:
- 繼承是侵入性的,只要繼承,就必須擁有父類的所有方法和屬性
- 降低了代碼的靈活性,子類必須擁有父類的屬性和方法,讓子類有了一些約束
- 增加了耦合性,當父類的常量,變量和方法被修改了,需要考慮子類的修改,這種修改可能帶來非常糟糕的結(jié)果,要重構(gòu)大量的代碼
四層含義
里氏替換原則包含以下4層含義:
- 子類可以實現(xiàn)父類的抽象方法,但是不能覆蓋父類的非抽象方法。
- 子類中可以增加自己特有的方法。
- 當子類覆蓋或?qū)崿F(xiàn)父類的方法時,方法的前置條件(即方法的形參)要比父類方法的輸入?yún)?shù)更寬松。
- 當子類的方法實現(xiàn)父類的抽象方法時,方法的后置條件(即方法的返回值)要比父類更嚴格。
【案例】正方形不是長方形。
在數(shù)學領(lǐng)域里,正方形毫無疑問是長方形,它是一個長寬相等的長方形。所以,我們開發(fā)的一個與幾何圖形相關(guān)的軟件系統(tǒng),就可以順理成章的讓正方形繼承自長方形。
代碼如下:
長方形類(Rectangle):
public class Rectangle {
private double length;
private double width;
public double getLength() {
return length;
}
public void setLength(double length) {
this.length = length;
}
public double getWidth() {
return width;
}
public void setWidth(double width) {
this.width = width;
}
}
正方形(Square):
由于正方形的長和寬相同,所以在方法setLength和setWidth中,對長度和寬度都需要賦相同值。
public class Square extends Rectangle {
public void setWidth(double width) {
super.setLength(width);
super.setWidth(width);
}
public void setLength(double length) {
super.setLength(length);
super.setWidth(length);
}
}
類RectangleDemo是我們的軟件系統(tǒng)中的一個組件,它有一個resize方法依賴基類Rectangle,resize方法是RectandleDemo類中的一個方法,用來實現(xiàn)寬度逐漸增長的效果。
public class RectangleDemo {
public static void resize(Rectangle rectangle) {
while (rectangle.getWidth() <= rectangle.getLength()) {
rectangle.setWidth(rectangle.getWidth() + 1);
}
}
//打印長方形的長和寬
public static void printLengthAndWidth(Rectangle rectangle) {
System.out.println(rectangle.getLength());
System.out.println(rectangle.getWidth());
}
public static void main(String[] args) {
Rectangle rectangle = new Rectangle();
rectangle.setLength(20);
rectangle.setWidth(10);
resize(rectangle);
printLengthAndWidth(rectangle);
System.out.println("============");
Rectangle rectangle1 = new Square();
rectangle1.setLength(10);
resize(rectangle1);
printLengthAndWidth(rectangle1);
}
}
我們運行一下這段代碼就會發(fā)現(xiàn),假如我們把一個普通長方形作為參數(shù)傳入resize方法,就會看到長方形寬度逐漸增長的效果,當寬度大于長度,代碼就會停止,這種行為的結(jié)果符合我們的預期;假如我們再把一個正方形作為參數(shù)傳入resize方法后,就會看到正方形的寬度和長度都在不斷增長,代碼會一直運行下去,直至系統(tǒng)產(chǎn)生溢出錯誤。
所以,普通的長方形是適合這段代碼的,正方形不適合。
我們得出結(jié)論:在resize方法中,Rectangle類型的參數(shù)是不能被Square類型的參數(shù)所代替,如果進行了替換就得不到預期結(jié)果。因此,Square類和Rectangle類之間的繼承關(guān)系違反了里氏代換原則,它們之間的繼承關(guān)系不成立,正方形不是長方形。
如何改進呢?此時我們需要重新設(shè)計他們之間的關(guān)系。抽象出來一個四邊形接口(Quadrilateral),讓Rectangle類和Square類實現(xiàn)Quadrilateral接口。文章來源:http://www.zghlxwxcb.cn/news/detail-626542.html
文章來源地址http://www.zghlxwxcb.cn/news/detail-626542.html
到了這里,關(guān)于基于面向?qū)ο蠡A(chǔ)設(shè)計——里氏替換原則的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!