1. 程式人生 > 其它 >【安裝—ES】ES資料庫安裝6.6b版本

【安裝—ES】ES資料庫安裝6.6b版本

里氏替換原則的定義

里氏替換原則(Liskov Substitution Principle,LSP)由麻省理工學院電腦科學實驗室的里斯科夫(Liskov)女士在 1987 年的“面向物件技術的高峰會議”(OOPSLA)上發表的一篇文章《資料抽象和層次》(Data Abstraction and Hierarchy)裡提出來的,她提出:繼承必須確保超類所擁有的性質在子類中仍然成立(Inheritance should ensure that any property proved about supertype objects also holds for subtype objects)。

里氏替換原則主要闡述了有關繼承的一些原則,也就是什麼時候應該使用繼承,什麼時候不應該使用繼承,以及其中蘊含的原理。里氏替換原是繼承複用的基礎,它反映了基類與子類之間的關係,是對開閉原則的補充,是對實現抽象化的具體步驟的規範。

里氏替換原則的作用

里氏替換原則的主要作用如下。

  1. 里氏替換原則是實現開閉原則的重要方式之一。
  2. 它克服了繼承中重寫父類造成的可複用性變差的缺點。
  3. 它是動作正確性的保證。即類的擴充套件不會給已有的系統引入新的錯誤,降低了程式碼出錯的可能性。
  4. 加強程式的健壯性,同時變更時可以做到非常好的相容性,提高程式的維護性、可擴充套件性,降低需求變更時引入的風險。

里氏替換原則的實現方法

里氏替換原則通俗來講就是:子類可以擴充套件父類的功能,但不能改變父類原有的功能。也就是說:子類繼承父類時,除新增新的方法完成新增功能外,儘量不要重寫父類的方法。

根據上述理解,對里氏替換原則的定義可以總結如下:

  • 子類可以實現父類的抽象方法,但不能覆蓋父類的非抽象方法
  • 子類中可以增加自己特有的方法
  • 當子類的方法過載父類的方法時,方法的前置條件(即方法的輸入引數)要比父類的方法更寬鬆
  • 當子類的方法實現父類的方法時(重寫/過載或實現抽象方法),方法的後置條件(即方法的的輸出/返回值)要比父類的方法更嚴格或相等


通過重寫父類的方法來完成新的功能寫起來雖然簡單,但是整個繼承體系的可複用性會比較差,特別是運用多型比較頻繁時,程式執行出錯的概率會非常大。

如果程式違背了里氏替換原則,則繼承類的物件在基類出現的地方會出現執行錯誤。這時其修正方法是:取消原來的繼承關係,重新設計它們之間的關係。

關於里氏替換原則的例子,最有名的是“正方形不是長方形”。當然,生活中也有很多類似的例子,例如,企鵝、鴕鳥和幾維鳥從生物學的角度來劃分,它們屬於鳥類;但從類的繼承關係來看,由於它們不能繼承“鳥”會飛的功能,所以它們不能定義成“鳥”的子類。同樣,由於“氣球魚”不會游泳,所以不能定義成“魚”的子類;“玩具炮”炸不了敵人,所以不能定義成“炮”的子類等。

下面以“幾維鳥不是鳥”為例來說明裡氏替換原則。

【例1】里氏替換原則在“幾維鳥不是鳥”例項中的應用。

分析:鳥一般都會飛行,如燕子的飛行速度大概是每小時 120 千米。但是紐西蘭的幾維鳥由於翅膀退化無法飛行。假如要設計一個例項,計算這兩種鳥飛行 300 千米要花費的時間。顯然,拿燕子來測試這段程式碼,結果正確,能計算出所需要的時間;但拿幾維鳥來測試,結果會發生“除零異常”或是“無窮大”,明顯不符合預期,其類圖如圖 1 所示。

程式程式碼如下:

package principle;
public class LSPtest {
    public static void main(String[] args) {
        Bird bird1 = new Swallow();
        Bird bird2 = new BrownKiwi();
        bird1.setSpeed(120);
        bird2.setSpeed(120);
        System.out.println("如果飛行300公里:");
        try {
            System.out.println("燕子將飛行" + bird1.getFlyTime(300) + "小時.");
            System.out.println("幾維鳥將飛行" + bird2.getFlyTime(300) + "小時。");
        } catch (Exception err) {
            System.out.println("發生錯誤了!");
        }
    }
}
//鳥類
class Bird {
    double flySpeed;
    public void setSpeed(double speed) {
        flySpeed = speed;
    }
    public double getFlyTime(double distance) {
        return (distance / flySpeed);
    }
}
//燕子類
class Swallow extends Bird {
}
//幾維鳥類
class BrownKiwi extends Bird {
    public void setSpeed(double speed) {
        flySpeed = 0;
    }
}

程式的執行結果如下:

如果飛行300公里:
燕子將飛行2.5小時.
幾維鳥將飛行Infinity小時

程式執行錯誤的原因是:幾維鳥類重寫了鳥類的 setSpeed(double speed) 方法,這違背了里氏替換原則。正確的做法是:取消幾維鳥原來的繼承關係,定義鳥和幾維鳥的更一般的父類,如動物類,它們都有奔跑的能力。幾維鳥的飛行速度雖然為 0,但奔跑速度不為 0,可以計算出其奔跑 300 千米所要花費的時間。其類圖如圖 2 所示。